Skip to content

Commit ec79319

Browse files
authored
Merge branch 'main' into ft/center-graph
2 parents a3948f7 + a9f44fe commit ec79319

File tree

18 files changed

+1381
-496
lines changed

18 files changed

+1381
-496
lines changed

Dockerfile

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# BUILD STAGE
2-
FROM node:24-slim@sha256:9b741b28148b0195d62fa456ed84dd6c953c1f17a3761f3e6e6797a754d9edff AS build-stage
2+
FROM node:24-slim@sha256:363eede750b6677a578eea4373235aaa70a7df0da90b5fe77f66b3e651484f6f AS build-stage
33
WORKDIR /usr/src/app
44

55
# Copy package.json and package-lock.json
@@ -18,16 +18,12 @@ RUN npm prune --omit=dev
1818

1919

2020
# PRODUCTION STAGE
21-
FROM gcr.io/distroless/nodejs24-debian12@sha256:98fd27d54e32d0d281a4c41db1bbe87a61259b2cad12c61bb1b8c0e32166162d AS production
21+
FROM gcr.io/distroless/nodejs24-debian12@sha256:f1572ff596ee17fe935a808c28a868f46dcbfad04003cb8fb951dd83025c16c9 AS production
2222
WORKDIR /usr/src/app
2323

2424
# Copy built files
25-
COPY --from=build-stage /usr/src/app/dist/client /usr/src/app/dist/client
26-
COPY --from=build-stage /usr/src/app/dist/vite.config.json /usr/src/app/dist/vite.config.json
27-
COPY --from=build-stage /usr/src/app/dist/server /usr/src/app/server
28-
COPY --from=build-stage /usr/src/app/dist/server.js /usr/src/app/server.js
29-
COPY --from=build-stage /usr/src/app/public /usr/src/app/public
25+
COPY --from=build-stage /usr/src/app/dist /usr/src/app/dist
3026
COPY --from=build-stage /usr/src/app/node_modules /usr/src/app/node_modules
3127

3228
# Run
33-
CMD ["server.js"]
29+
CMD ["dist/server.js"]

package-lock.json

Lines changed: 1030 additions & 267 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
},
99
"scripts": {
1010
"dev": "node --loader ts-node/esm ./server.ts --local-dev",
11-
"start": "node dist/server/server.js",
11+
"start": "node dist/server.js",
1212
"build": "tsc && npm run build:server && vite build",
1313
"build:server": "tsc -p tsconfig.server.json",
1414
"lint": "eslint ./src --report-unused-disable-directives --max-warnings 0",
@@ -73,12 +73,12 @@
7373
"@types/js-yaml": "^4.0.9",
7474
"@types/node": "^22.13.5",
7575
"@types/react": "^19.0.10",
76-
"@types/react-dom": "19.1.7",
76+
"@types/react-dom": "19.1.9",
7777
"@types/react-syntax-highlighter": "^15.5.13",
7878
"@ui5/webcomponents-cypress-commands": "^2.7.2",
7979
"@vitejs/plugin-react": "^5.0.0",
8080
"@vitest/eslint-plugin": "^1.1.37",
81-
"cypress": "^14.1.0",
81+
"cypress": "^15.0.0",
8282
"eslint-config-prettier": "^10.1.1",
8383
"eslint-import-resolver-typescript": "^4.1.1",
8484
"eslint-plugin-i18next": "^6.1.3",

server.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { injectDynatraceTag } from './server/config/dynatrace.js';
1212

1313
dotenv.config();
1414

15+
console.log(process.env);
16+
1517
const { DYNATRACE_SCRIPT_URL } = process.env;
1618
if (DYNATRACE_SCRIPT_URL) {
1719
injectDynatraceTag(DYNATRACE_SCRIPT_URL);
@@ -50,7 +52,11 @@ if (process.env.FRONTEND_CONFIG_PATH !== undefined && process.env.FRONTEND_CONFI
5052
}
5153

5254
// Make hyperspace portal configuration available (hyperspace-portal-config.json)
53-
if (!isLocalDev && process.env.HYPERSPACE_PORTAL_CONFIG_PATH !== undefined && process.env.HYPERSPACE_PORTAL_CONFIG_PATH.length > 0) {
55+
if (
56+
!isLocalDev &&
57+
process.env.HYPERSPACE_PORTAL_CONFIG_PATH !== undefined &&
58+
process.env.HYPERSPACE_PORTAL_CONFIG_PATH.length > 0
59+
) {
5460
const hyperspacePortalConfigLocation = 'dist/client/hyperspace-portal-config.json';
5561
console.log('HYPERSPACE_PORTAL_CONFIG_PATH is specified. Will copy the hyperspace-portal-config from there.');
5662
console.log(` Copying ${process.env.HYPERSPACE_PORTAL_CONFIG_PATH} to ${hyperspacePortalConfigLocation}`);
@@ -85,7 +91,6 @@ if (DYNATRACE_SCRIPT_URL) {
8591
}
8692
}
8793

88-
8994
fastify.register(helmet, {
9095
contentSecurityPolicy: {
9196
directives: {

src/AppRouter.tsx

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,21 @@ import { ShellBarComponent } from './components/Core/ShellBar.tsx';
55
import { SentryRoutes } from './mount.ts';
66
import ProjectPage from './spaces/onboarding/pages/ProjectPage.tsx';
77
import McpPage from './spaces/mcp/pages/McpPage.tsx';
8+
import { SearchParamToggleVisibility } from './components/Helper/FeatureToggleExistance.tsx';
89

910
function AppRouter() {
1011
return (
1112
<>
12-
<ShellBarComponent />
13+
<SearchParamToggleVisibility
14+
shouldBeVisible={(params) => {
15+
if (params === undefined) return true;
16+
if (params.get('showHeaderBar') === null) return true;
17+
return params?.get('showHeaderBar') === 'true';
18+
}}
19+
>
20+
<ShellBarComponent />
21+
</SearchParamToggleVisibility>
22+
1323
<Router>
1424
<SentryRoutes>
1525
<Route path="/mcp" element={<GlobalProviderOutlet />}>

src/components/ComponentsSelection/ComponentsSelectionContainer.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,12 +69,12 @@ export const ComponentsSelectionContainer: React.FC<ComponentsSelectionProps> =
6969
}
7070

7171
if (error) {
72-
return <IllustratedError />;
72+
return <IllustratedError compact={true} />;
7373
}
7474

7575
// Defensive: If the API returned no items, show error
7676
if (!componentsList || componentsList.length === 0) {
77-
return <IllustratedError title={t('componentsSelection.cannotLoad')} />;
77+
return <IllustratedError title={t('componentsSelection.cannotLoad')} compact={true} />;
7878
}
7979

8080
return <ComponentsSelection componentsList={componentsList} setComponentsList={setComponentsList} />;

src/components/ControlPlane/FluxList.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,7 @@ export default function FluxList() {
136136
<IllustratedError
137137
details={repoErr?.message || kustomizationErr?.message || t('FluxList.undefinedError')}
138138
title={t('FluxList.noFluxError')}
139+
compact={true}
139140
/>
140141
);
141142
}

src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ export function ControlPlaneListWorkspaceGridTile({ projectName, workspace }: Pr
5858
<IllustratedError
5959
title={t('ControlPlaneListWorkspaceGridTile.permissionErrorMessage')}
6060
details={t('ControlPlaneListWorkspaceGridTile.permissionErrorMessageSubtitle')}
61+
compact={true}
6162
/>
6263
);
6364
} else {

src/components/Core/BetaButton.tsx

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { ButtonDomRef, Button, Icon, PopoverDomRef, Popover, Text } from '@ui5/webcomponents-react';
2+
import { useState, useRef, RefObject } from 'react';
3+
import styles from './ShellBar.module.css';
4+
import { useTranslation } from 'react-i18next';
5+
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
6+
import { ThemingParameters } from '@ui5/webcomponents-react-base';
7+
8+
export function BetaButton() {
9+
const [betaPopoverOpen, setBetaPopoverOpen] = useState(false);
10+
const betaButtonRef = useRef<ButtonDomRef>(null);
11+
const betaPopoverRef = useRef<PopoverDomRef>(null);
12+
const { t } = useTranslation();
13+
14+
const onBetaClick = () => {
15+
if (betaButtonRef.current) {
16+
betaPopoverRef.current!.opener = betaButtonRef.current;
17+
setBetaPopoverOpen(!betaPopoverOpen);
18+
}
19+
};
20+
21+
return (
22+
<>
23+
<Button ref={betaButtonRef} className={styles.betaButton} onClick={onBetaClick}>
24+
<span className={styles.betaContent}>
25+
<Icon name="information" className={styles.betaIcon} />
26+
<span className={styles.betaText}>{t('ShellBar.betaButton')}</span>
27+
</span>
28+
<BetaPopover open={betaPopoverOpen} setOpen={setBetaPopoverOpen} popoverRef={betaPopoverRef} />
29+
</Button>
30+
</>
31+
);
32+
}
33+
34+
const BetaPopover = ({
35+
open,
36+
setOpen,
37+
popoverRef,
38+
}: {
39+
open: boolean;
40+
setOpen: (arg0: boolean) => void;
41+
popoverRef: RefObject<PopoverDomRef | null>;
42+
}) => {
43+
const { t } = useTranslation();
44+
45+
return (
46+
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
47+
<Text
48+
style={{
49+
padding: '1rem',
50+
maxWidth: '250px',
51+
fontFamily: ThemingParameters.sapFontFamily,
52+
}}
53+
>
54+
{t('ShellBar.betaButtonDescription')}
55+
</Text>
56+
</Popover>
57+
);
58+
};
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import {
2+
PopoverDomRef,
3+
Ui5CustomEvent,
4+
TextAreaDomRef,
5+
Button,
6+
ButtonDomRef,
7+
Popover,
8+
Form,
9+
FormGroup,
10+
FormItem,
11+
Label,
12+
Link,
13+
RatingIndicator,
14+
TextArea,
15+
} from '@ui5/webcomponents-react';
16+
import { Dispatch, RefObject, SetStateAction, useRef, useState } from 'react';
17+
import { useAuthOnboarding } from '../../spaces/onboarding/auth/AuthContextOnboarding';
18+
import { useTranslation } from 'react-i18next';
19+
import { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js';
20+
import PopoverPlacement from '@ui5/webcomponents/dist/types/PopoverPlacement.js';
21+
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
22+
23+
type UI5RatingIndicatorElement = HTMLElement & { value: number };
24+
25+
export function FeedbackButton() {
26+
const feedbackPopoverRef = useRef<PopoverDomRef>(null);
27+
const [feedbackMessage, setFeedbackMessage] = useState('');
28+
const [feedbackSent, setFeedbackSent] = useState(false);
29+
const [feedbackPopoverOpen, setFeedbackPopoverOpen] = useState(false);
30+
const [rating, setRating] = useState(0);
31+
const { user } = useAuthOnboarding();
32+
33+
const onFeedbackClick = (e: Ui5CustomEvent<ButtonDomRef, ButtonClickEventDetail>) => {
34+
feedbackPopoverRef.current!.opener = e.target;
35+
setFeedbackPopoverOpen(!feedbackPopoverOpen);
36+
};
37+
38+
const onFeedbackMessageChange = (event: Ui5CustomEvent<TextAreaDomRef, { value: string; previousValue: string }>) => {
39+
const newValue = event.target.value;
40+
setFeedbackMessage(newValue);
41+
};
42+
43+
async function onFeedbackSent() {
44+
const payload = {
45+
message: feedbackMessage,
46+
rating: rating.toString(),
47+
user: user?.email,
48+
environment: window.location.hostname,
49+
};
50+
try {
51+
await fetch('/api/feedback', {
52+
method: 'POST',
53+
headers: {
54+
'Content-Type': 'application/json',
55+
},
56+
body: JSON.stringify(payload),
57+
});
58+
} catch (err) {
59+
console.log(err);
60+
} finally {
61+
setFeedbackSent(true);
62+
}
63+
}
64+
65+
return (
66+
<>
67+
<Button icon="feedback" tooltip="Feedback" design={ButtonDesign.Transparent} onClick={onFeedbackClick} />
68+
<FeedbackPopover
69+
open={feedbackPopoverOpen}
70+
setOpen={setFeedbackPopoverOpen}
71+
popoverRef={feedbackPopoverRef}
72+
setRating={setRating}
73+
rating={rating}
74+
feedbackMessage={feedbackMessage}
75+
feedbackSent={feedbackSent}
76+
onFeedbackSent={onFeedbackSent}
77+
onFeedbackMessageChange={onFeedbackMessageChange}
78+
/>
79+
</>
80+
);
81+
}
82+
83+
const FeedbackPopover = ({
84+
open,
85+
setOpen,
86+
popoverRef,
87+
setRating,
88+
rating,
89+
onFeedbackSent,
90+
feedbackMessage,
91+
onFeedbackMessageChange,
92+
feedbackSent,
93+
}: {
94+
open: boolean;
95+
setOpen: (arg0: boolean) => void;
96+
popoverRef: RefObject<PopoverDomRef | null>;
97+
setRating: Dispatch<SetStateAction<number>>;
98+
rating: number;
99+
onFeedbackSent: () => void;
100+
feedbackMessage: string;
101+
onFeedbackMessageChange: (
102+
event: Ui5CustomEvent<
103+
TextAreaDomRef,
104+
{
105+
value: string;
106+
previousValue: string;
107+
}
108+
>,
109+
) => void;
110+
feedbackSent: boolean;
111+
}) => {
112+
const { t } = useTranslation();
113+
114+
const onRatingChange = (event: Event & { target: UI5RatingIndicatorElement }) => {
115+
setRating(event.target.value);
116+
};
117+
118+
return (
119+
<>
120+
<Popover ref={popoverRef} placement={PopoverPlacement.Bottom} open={open} onClose={() => setOpen(false)}>
121+
<div
122+
style={{
123+
padding: '1rem',
124+
width: '250px',
125+
}}
126+
>
127+
{!feedbackSent ? (
128+
<Form headerText={t('ShellBar.feedbackHeader')}>
129+
<FormGroup>
130+
<FormItem labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackRatingLabel')}</Label>}>
131+
<RatingIndicator value={rating} max={5} onChange={onRatingChange} />
132+
</FormItem>
133+
<FormItem
134+
className="formAlignLabelStart"
135+
labelContent={<Label style={{ color: 'black' }}>{t('ShellBar.feedbackMessageLabel')}</Label>}
136+
>
137+
<TextArea
138+
value={feedbackMessage}
139+
placeholder={t('ShellBar.feedbackPlaceholder')}
140+
rows={5}
141+
onInput={onFeedbackMessageChange}
142+
/>
143+
</FormItem>
144+
<FormItem>
145+
<Button design="Emphasized" onClick={() => onFeedbackSent()}>
146+
{t('ShellBar.feedbackButton')}
147+
</Button>
148+
</FormItem>
149+
<FormItem>
150+
<Label style={{ color: 'gray' }}>
151+
{t('ShellBar.feedbackNotificationText')}
152+
<Link
153+
href="https://github.com/openmcp-project/ui-frontend/issues/new/choose"
154+
target="_blank"
155+
rel="noreferrer"
156+
>
157+
{t('ShellBar.feedbackNotificationAction')}
158+
</Link>
159+
</Label>
160+
</FormItem>
161+
</FormGroup>
162+
</Form>
163+
) : (
164+
<Label>{t('ShellBar.feedbackThanks')}</Label>
165+
)}
166+
</div>
167+
</Popover>
168+
</>
169+
);
170+
};

0 commit comments

Comments
 (0)