Skip to content

Commit b646f40

Browse files
Merge branch 'main' into renovate/node-22
2 parents 02af248 + 6903931 commit b646f40

23 files changed

+1105
-177
lines changed

.env.template

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ API_BACKEND_URL=
1515
# Replace this value with a strong, randomly generated string (at least 32 characters).
1616
# Example for generation in Node.js: require('crypto').randomBytes(32).toString('hex')
1717
COOKIE_SECRET=
18+
19+
FEEDBACK_SLACK_URL=
20+
FEEDBACK_URL_LINK=

public/locales/en.json

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,9 @@
4040
"tableHeaderUsage": "Usage"
4141
},
4242
"ControlPlaneListToolbar": {
43-
"buttonText": "Workspace"
43+
"buttonText": "Workspace",
44+
"deleteWorkspace": "Delete workspace",
45+
"createNewManagedControlPlane": "Create new Managed Control Plane"
4446
},
4547
"ControlPlaneListWorkspaceGridTile": {
4648
"deleteConfirmationDialog": "Workspace deletion triggered. The list will refresh automatically once completed.",
@@ -79,8 +81,8 @@
7981
"menuCopy": "Copy to clipboard"
8082
},
8183
"IllustratedBanner": {
82-
"titleMessage": "No ManagedControlPlane",
83-
"subtitleMessage": "Create a ManagedControlPlane to get started",
84+
"titleMessage": "No Managed Control Planes found",
85+
"subtitleMessage": "Get started by creating your first Managed Control Plane.",
8486
"helpButton": "Help"
8587
},
8688
"IntelligentBreadcrumbs": {
@@ -95,7 +97,15 @@
9597
},
9698
"ShellBar": {
9799
"betaButtonDescription": "This web app is currently in Beta, and may not ready for productive use. We're actively improving the experience and would love your feedback — your input helps shape the future of the app!",
98-
"signOutButton": "Sign Out"
100+
"signOutButton": "Sign Out",
101+
"feedbackMessageLabel": "Message",
102+
"feedbackRatingLabel": "Rating",
103+
"feedbackHeader": "Your feedback",
104+
"feedbackButton": "Send feedback",
105+
"feedbackButtonInfo": "Give us your feedback",
106+
"feedbackPlaceholder": "Please let us know what you think about our application",
107+
"feedbackNotification": "*Slack notification with your email address will be shared with our Operations Team. If you have a special Feature request in mind, please create here.",
108+
"feedbackThanks": "Thank you for your feedback!"
99109
},
100110
"CreateProjectDialog": {
101111
"toastMessage": "Project creation triggered. The list will refresh automatically once completed."
@@ -256,21 +266,39 @@
256266
"validationErrors": {
257267
"required": "This field is required!",
258268
"properFormatting": "Use A-Z, a-z, 0-9, hyphen (-), and period (.), but note that whitespace (spaces, tabs, etc.) is not allowed for proper compatibility.",
269+
"properFormattingLowercase": "Use lowercase a-z, 0-9, hyphen (-), and period (.), but note that whitespace (spaces, tabs, etc.) is not allowed for proper compatibility.",
259270
"max25chars": "Max length is 25 characters.",
260271
"userExists": "User with this email already exists!",
261272
"atLeastOneUser": "You need to have at least one member assigned."
262273
},
263274
"common": {
264275
"close": "Close",
265-
"cannotLoadData": "Cannot load data"
276+
"cannotLoadData": "Cannot load data",
277+
"metadata": "Metadata",
278+
"members": "Members",
279+
"summarize": "Summarize",
280+
"namespace": "Namespace",
281+
"region": "Region",
282+
"success": "Success",
283+
"displayName": "Display Name",
284+
"name": "Name"
266285
},
267286
"buttons": {
268287
"viewResource": "View resource",
269288
"download": "Download",
270-
"copy": "Copy"
289+
"copy": "Copy",
290+
"next": "Next",
291+
"create": "Create",
292+
"close": "Close",
293+
"back": "Back"
271294
},
272295
"yaml": {
273296
"copiedToClipboard": "YAML copied to clipboard!",
274297
"YAML": "YAML"
298+
},
299+
"createMCP": {
300+
"dialogTitle": "Create Managed Control Plane",
301+
"titleText": "Managed Control Plane Created Successfully!",
302+
"subtitleText": "Your Managed Control Plane is being set up. It will be ready to use in just a few minutes. You can safely close this window."
275303
}
276304
}

server/config/env.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ const schema = {
1313
POST_LOGIN_REDIRECT: { type: "string" },
1414
COOKIE_SECRET: { type: "string" },
1515
API_BACKEND_URL: { type: "string" },
16+
FEEDBACK_SLACK_URL: { type: "string" },
17+
FEEDBACK_URL_LINK: { type: "string" },
1618

1719
// System variables
1820
NODE_ENV: { type: "string", enum: ["development", "production"] },

server/routes/feedback.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import fetch from 'node-fetch';
2+
import fp from "fastify-plugin";
3+
4+
async function feedbackRoute(fastify) {
5+
const { FEEDBACK_SLACK_URL } = fastify.config;
6+
7+
fastify.post('/feedback', async (request, reply) => {
8+
const { message, rating, user, environment } = request.body;
9+
10+
if (!message || !rating || !user || !environment) {
11+
return reply.status(400).send({ error: 'Missing required fields' });
12+
}
13+
14+
try {
15+
const res = await fetch(FEEDBACK_SLACK_URL, {
16+
method: 'POST',
17+
headers: {
18+
'Content-Type': 'application/json',
19+
},
20+
body: JSON.stringify({ message, rating, user, environment }),
21+
});
22+
23+
if (!res.ok) {
24+
return reply.status(500).send({ error: 'Slack API error' });
25+
}
26+
return reply.send({ message: res, });
27+
} catch (err) {
28+
fastify.log.error('Slack error:', err);
29+
return reply.status(500).send({ error: 'Request failed' });
30+
}
31+
});
32+
}
33+
34+
export default fp(feedbackRoute);
35+
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
import {
2+
Button,
3+
ButtonDomRef,
4+
Menu,
5+
MenuItem,
6+
Ui5CustomEvent,
7+
MenuDomRef,
8+
} from '@ui5/webcomponents-react';
9+
import type { ButtonClickEventDetail } from '@ui5/webcomponents/dist/Button.js';
10+
import { Dispatch, FC, SetStateAction, useRef, useState } from 'react';
11+
import '@ui5/webcomponents-icons/dist/copy';
12+
import '@ui5/webcomponents-icons/dist/accept';
13+
14+
import { useTranslation } from 'react-i18next';
15+
16+
type ControlPlanesListMenuProps = {
17+
setDialogDeleteWsIsOpen: Dispatch<SetStateAction<boolean>>;
18+
setIsCreateManagedControlPlaneWizardOpen: Dispatch<SetStateAction<boolean>>;
19+
};
20+
21+
export const ControlPlanesListMenu: FC<ControlPlanesListMenuProps> = ({
22+
setDialogDeleteWsIsOpen,
23+
setIsCreateManagedControlPlaneWizardOpen,
24+
}) => {
25+
const popoverRef = useRef<MenuDomRef>(null);
26+
const [open, setOpen] = useState(false);
27+
28+
const { t } = useTranslation();
29+
30+
const handleOpenerClick = (
31+
e: Ui5CustomEvent<ButtonDomRef, ButtonClickEventDetail>,
32+
) => {
33+
if (popoverRef.current && e.currentTarget) {
34+
popoverRef.current.opener = e.currentTarget as HTMLElement;
35+
setOpen((prev) => !prev);
36+
}
37+
};
38+
39+
return (
40+
<>
41+
<Button icon="overflow" icon-end onClick={handleOpenerClick} />
42+
<Menu
43+
ref={popoverRef}
44+
open={open}
45+
onItemClick={(event) => {
46+
const action = (event.detail.item as HTMLElement).dataset.action;
47+
if (action === 'newManagedControlPlane') {
48+
setIsCreateManagedControlPlaneWizardOpen(true);
49+
}
50+
if (action === 'deleteWorkspace') {
51+
setDialogDeleteWsIsOpen(true);
52+
}
53+
54+
setOpen(false);
55+
}}
56+
>
57+
<MenuItem
58+
key={'add'}
59+
text={t('ControlPlaneListToolbar.createNewManagedControlPlane')}
60+
data-action="newManagedControlPlane"
61+
icon="add"
62+
/>
63+
<MenuItem
64+
key={'delete'}
65+
text={t('ControlPlaneListToolbar.deleteWorkspace')}
66+
data-action="deleteWorkspace"
67+
icon="delete"
68+
/>
69+
</Menu>
70+
</>
71+
);
72+
};

src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,9 @@ import { YamlViewButtonWithLoader } from '../../Yaml/YamlViewButtonWithLoader.ts
3737
import { IllustratedBanner } from '../../Ui/IllustratedBanner/IllustratedBanner.tsx';
3838
import { useLink } from '../../../lib/shared/useLink.ts';
3939
import IllustrationMessageType from '@ui5/webcomponents-fiori/dist/types/IllustrationMessageType.js';
40+
import styles from './WorkspacesList.module.css';
41+
import { ControlPlanesListMenu } from '../ControlPlanesListMenu.tsx';
42+
import { CreateManagedControlPlaneWizardContainer } from '../../Wizards/CreateManagedControlPlaneWizardContainer.tsx';
4043

4144
interface Props {
4245
projectName: string;
@@ -47,6 +50,10 @@ export function ControlPlaneListWorkspaceGridTile({
4750
projectName,
4851
workspace,
4952
}: Props) {
53+
const [
54+
isCreateManagedControlPlaneWizardOpen,
55+
setIsCreateManagedControlPlaneWizardOpen,
56+
] = useState(false);
5057
const workspaceName = workspace.metadata.name;
5158
const workspaceDisplayName =
5259
workspace.metadata.annotations?.[DISPLAY_NAME_ANNOTATION] || '';
@@ -103,7 +110,6 @@ export function ControlPlaneListWorkspaceGridTile({
103110
<Panel
104111
headerLevel="H2"
105112
style={{ margin: '12px 12px 12px 0' }}
106-
collapsed={controlplanes?.length === 0}
107113
header={
108114
<div
109115
style={{
@@ -131,18 +137,17 @@ export function ControlPlaneListWorkspaceGridTile({
131137
workspace={workspaceName}
132138
/>
133139
<FlexBox justifyContent={'SpaceBetween'} gap={10}>
134-
<Button
135-
design={'Transparent'}
136-
icon="delete"
137-
onClick={async () => {
138-
setDialogDeleteWsIsOpen(true);
139-
}}
140-
/>
141140
<YamlViewButtonWithLoader
142141
workspaceName={workspace.metadata.namespace}
143142
resourceName={workspaceName}
144143
resourceType={'workspaces'}
145144
/>
145+
<ControlPlanesListMenu
146+
setDialogDeleteWsIsOpen={setDialogDeleteWsIsOpen}
147+
setIsCreateManagedControlPlaneWizardOpen={
148+
setIsCreateManagedControlPlaneWizardOpen
149+
}
150+
/>
146151
</FlexBox>
147152
</div>
148153
}
@@ -159,6 +164,18 @@ export function ControlPlaneListWorkspaceGridTile({
159164
link: mcpCreationGuide,
160165
buttonText: t('IllustratedBanner.helpButton'),
161166
}}
167+
button={
168+
<Button
169+
className={styles.createButton}
170+
icon={'add'}
171+
design={'Emphasized'}
172+
onClick={() => {
173+
setIsCreateManagedControlPlaneWizardOpen(true);
174+
}}
175+
>
176+
{t('ControlPlaneListToolbar.createNewManagedControlPlane')}
177+
</Button>
178+
}
162179
/>
163180
) : (
164181
<Grid defaultSpan="XL4 L4 M7 S12">
@@ -191,6 +208,12 @@ export function ControlPlaneListWorkspaceGridTile({
191208
);
192209
}}
193210
/>
211+
<CreateManagedControlPlaneWizardContainer
212+
isOpen={isCreateManagedControlPlaneWizardOpen}
213+
setIsOpen={setIsCreateManagedControlPlaneWizardOpen}
214+
projectName={projectNamespace}
215+
workspaceName={workspaceName}
216+
/>
194217
</>
195218
);
196219
}

src/components/ControlPlanes/List/MembersAvatarView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ export function MembersAvatarView({ members, project, workspace }: Props) {
4949
setPopoverIsOpen(false);
5050
}}
5151
>
52-
<MemberTable members={members} />
52+
<MemberTable members={members} requireAtLeastOneMember={false} />
5353
</Popover>
5454
</div>
5555
);
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.createButton {
2+
margin-bottom: 2rem;
3+
}

0 commit comments

Comments
 (0)