Skip to content

Commit d4fd7cb

Browse files
authored
Feature/display resource yaml (#81)
Added a feature that displays YAML files for projects, namespaces and MCPs.
1 parent efa9c27 commit d4fd7cb

File tree

15 files changed

+1239
-1130
lines changed

15 files changed

+1239
-1130
lines changed

package-lock.json

Lines changed: 905 additions & 1046 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 & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@
3939
"react-i18next": "^15.4.1",
4040
"react-oidc-context": "^3.2.0",
4141
"react-router-dom": "^7.2.0",
42+
"react-syntax-highlighter": "^15.6.1",
4243
"react-time-ago": "^7.3.3",
4344
"swr": "^2.3.0",
45+
"yaml": "^2.7.1",
4446
"zod": "^3.24.2"
4547
},
4648
"devDependencies": {
@@ -52,6 +54,7 @@
5254
"@types/node": "^22.13.5",
5355
"@types/react": "^19.0.10",
5456
"@types/react-dom": "19.1.2",
57+
"@types/react-syntax-highlighter": "^15.5.13",
5558
"@ui5/webcomponents-cypress-commands": "^2.7.2",
5659
"@vitejs/plugin-react": "^4.3.4",
5760
"@vitest/eslint-plugin": "^1.1.37",

public/locales/en.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,5 +239,17 @@
239239
"max25chars": "Max length is 25 characters.",
240240
"userExists": "User with this email already exists!",
241241
"atLeastOneUser": "You need to have at least one member assigned."
242+
},
243+
"common": {
244+
"close": "Close",
245+
"cannotLoadData": "Cannot load data"
246+
},
247+
"buttons": {
248+
"viewResource": "View resource",
249+
"download": "Download",
250+
"copy": "Copy"
251+
},
252+
"yaml": {
253+
"copiedToClipboard": "YAML copied to clipboard!"
242254
}
243255
}

src/components/ControlPlanes/ConnectButton.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ export default function ConnectButton(props: Props) {
5555
if (contexts.length === 1) {
5656
return (
5757
<Button
58+
endIcon={'navigation-right-arrow'}
5859
disabled={props.disabled}
5960
onClick={() =>
6061
navigate(

src/components/ControlPlanes/ControlPlaneCard/ControlPlaneCard.tsx

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,27 @@ import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
33
import '@ui5/webcomponents-fiori/dist/illustrations/EmptyList.js';
44
import '@ui5/webcomponents-icons/dist/delete';
55
import ConnectButton from '../ConnectButton.tsx';
6-
import { ListWorkspacesType } from '../../../lib/api/types/crate/listWorkspaces.ts';
6+
7+
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
8+
import { useState } from 'react';
9+
10+
import { DeleteConfirmationDialog } from '../../Dialogs/DeleteConfirmationDialog.tsx';
11+
import MCPHealthPopoverButton from '../../ControlPlane/MCPHealthPopoverButton.tsx';
12+
import styles from './ControlPlaneCard.module.css';
13+
import { KubectlDeleteMcp } from '../../Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx';
714
import {
815
ListControlPlanesType,
916
ReadyStatus,
1017
} from '../../../lib/api/types/crate/controlPlanes.ts';
11-
import TitleLevel from '@ui5/webcomponents/dist/types/TitleLevel.js';
12-
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
13-
import { useState } from 'react';
18+
import { ListWorkspacesType } from '../../../lib/api/types/crate/listWorkspaces.ts';
1419
import { useApiResourceMutation } from '../../../lib/api/useApiResource.ts';
1520
import {
1621
DeleteMCPResource,
1722
DeleteMCPType,
1823
PatchMCPResourceForDeletion,
1924
PatchMCPResourceForDeletionBody,
2025
} from '../../../lib/api/types/crate/deleteMCP.ts';
21-
import { DeleteConfirmationDialog } from '../../Dialogs/DeleteConfirmationDialog.tsx';
22-
import MCPHealthPopoverButton from '../../ControlPlane/MCPHealthPopoverButton.tsx';
23-
import styles from './ControlPlaneCard.module.css';
24-
import { KubectlDeleteMcp } from '../../Dialogs/KubectlCommandInfo/Controllers/KubectlDeleteMcp.tsx';
26+
import { YamlViewButton } from '../../Yaml/YamlViewButton.tsx';
2527

2628
interface Props {
2729
controlPlane: ListControlPlanesType;
@@ -64,7 +66,6 @@ export function ControlPlaneCard({
6466
</FlexBox>
6567
<div>
6668
<Button
67-
design={ButtonDesign.Transparent}
6869
icon="delete"
6970
disabled={
7071
controlPlane.status?.status === ReadyStatus.InDeletion
@@ -82,15 +83,27 @@ export function ControlPlaneCard({
8283
className={styles.row}
8384
>
8485
<MCPHealthPopoverButton mcpStatus={controlPlane.status} />
85-
<ConnectButton
86-
disabled={controlPlane.status?.status !== ReadyStatus.Ready}
87-
controlPlaneName={name}
88-
projectName={projectName}
89-
workspaceName={workspace.metadata.name ?? ''}
90-
namespace={controlPlane.status?.access?.namespace ?? ''}
91-
secretName={controlPlane.status?.access?.name ?? ''}
92-
secretKey={controlPlane.status?.access?.key ?? ''}
93-
/>
86+
<FlexBox
87+
direction="Row"
88+
justifyContent="SpaceBetween"
89+
alignItems="Center"
90+
gap={10}
91+
>
92+
<YamlViewButton
93+
workspaceName={controlPlane.metadata.namespace}
94+
resourceName={controlPlane.metadata.name}
95+
resourceType={'managedcontrolplanes'}
96+
/>
97+
<ConnectButton
98+
disabled={controlPlane.status?.status !== ReadyStatus.Ready}
99+
controlPlaneName={name}
100+
projectName={projectName}
101+
workspaceName={workspace.metadata.name ?? ''}
102+
namespace={controlPlane.status?.access?.namespace ?? ''}
103+
secretName={controlPlane.status?.access?.name ?? ''}
104+
secretKey={controlPlane.status?.access?.key ?? ''}
105+
/>
106+
</FlexBox>
94107
</FlexBox>
95108
</FlexBox>
96109
</div>

src/components/ControlPlanes/List/ControlPlaneListWorkspaceGridTile.tsx

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import {
22
Button,
3+
FlexBox,
34
Grid,
45
ObjectPageSection,
56
Panel,
@@ -8,7 +9,6 @@ import {
89
import '@ui5/webcomponents-fiori/dist/illustrations/NoData.js';
910
import '@ui5/webcomponents-fiori/dist/illustrations/EmptyList.js';
1011
import '@ui5/webcomponents-icons/dist/delete';
11-
import ButtonDesign from '@ui5/webcomponents/dist/types/ButtonDesign.js';
1212
import { CopyButton } from '../../Shared/CopyButton.tsx';
1313
import { NoManagedControlPlaneBanner } from '../NoManagedControlPlaneBanner.tsx';
1414
import { ControlPlaneCard } from '../ControlPlaneCard/ControlPlaneCard.tsx';
@@ -34,6 +34,7 @@ import { ListControlPlanes } from '../../../lib/api/types/crate/controlPlanes.ts
3434
import IllustratedError from '../../Shared/IllustratedError.tsx';
3535
import { APIError } from '../../../lib/api/error.ts';
3636
import { useTranslation } from 'react-i18next';
37+
import { YamlViewButton } from '../../Yaml/YamlViewButton.tsx';
3738

3839
interface Props {
3940
projectName: string;
@@ -104,8 +105,9 @@ export function ControlPlaneListWorkspaceGridTile({
104105
style={{
105106
width: '100%',
106107
display: 'grid',
107-
gridTemplateColumns: '0.3fr 0.24fr auto 0.05fr',
108+
gridTemplateColumns: '0.3fr 0.3fr 0.24fr auto',
108109
gap: '1rem',
110+
justifyContent: 'space-between',
109111
alignItems: 'center',
110112
}}
111113
>
@@ -118,18 +120,25 @@ export function ControlPlaneListWorkspaceGridTile({
118120
text={workspace.status?.namespace || '-'}
119121
style={{ justifyContent: 'start' }}
120122
/>
123+
121124
<MembersAvatarView
122125
members={workspace.spec.members}
123126
project={projectName}
124127
workspace={workspaceName}
125128
/>
126-
<Button
127-
design={ButtonDesign.Transparent}
128-
icon="delete"
129-
onClick={async () => {
130-
setDialogDeleteWsIsOpen(true);
131-
}}
132-
/>
129+
<FlexBox justifyContent={'SpaceBetween'} gap={10}>
130+
<Button
131+
icon="delete"
132+
onClick={async () => {
133+
setDialogDeleteWsIsOpen(true);
134+
}}
135+
/>
136+
<YamlViewButton
137+
workspaceName={workspace.metadata.namespace}
138+
resourceName={workspaceName}
139+
resourceType={'workspaces'}
140+
/>
141+
</FlexBox>
133142
</div>
134143
}
135144
noAnimation

src/components/Projects/ProjectsList.tsx

Lines changed: 64 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,78 @@ import { CopyButton } from '../Shared/CopyButton.tsx';
44
import useLuigiNavigate from '../Shared/useLuigiNavigate.tsx';
55
import IllustratedError from '../Shared/IllustratedError.tsx';
66
import useResource from '../../lib/api/useApiResource';
7-
import { projectnameToNamespace } from '../../utils/index';
7+
import { projectnameToNamespace } from '../../utils';
88
import '@ui5/webcomponents-icons/dist/copy';
99
import '@ui5/webcomponents-icons/dist/arrow-right';
1010
import { ListProjectNames } from '../../lib/api/types/crate/listProjectNames';
1111
import { t } from 'i18next';
12+
import { YamlViewButton } from '../Yaml/YamlViewButton.tsx';
13+
import { useMemo } from 'react';
1214

1315
export default function ProjectsList() {
1416
const navigate = useLuigiNavigate();
1517
const { data, error } = useResource(ListProjectNames, {
1618
refreshInterval: 3000,
1719
});
20+
const stabilizedData = useMemo(
21+
() =>
22+
data?.map((projectName) => {
23+
return {
24+
projectName: projectName,
25+
nameSpace: projectnameToNamespace(projectName),
26+
};
27+
}) ?? [],
28+
[data],
29+
);
30+
const stabilizedColumns = useMemo(
31+
() => [
32+
{
33+
Header: t('ProjectsListView.title'),
34+
accessor: 'projectName',
35+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
36+
Cell: (instance: any) => (
37+
<div
38+
style={{
39+
cursor: 'pointer',
40+
width: '100%',
41+
color: ThemingParameters.sapLinkColor,
42+
fontWeight: 'bold',
43+
display: 'flex',
44+
justifyContent: 'space-between',
45+
gap: '1rem',
46+
alignItems: 'center',
47+
}}
48+
>
49+
{instance.cell.value}
50+
<YamlViewButton
51+
resourceType={'projects'}
52+
resourceName={instance.cell.value}
53+
/>
54+
</div>
55+
),
56+
},
57+
{
58+
Header: 'Namespace',
59+
accessor: 'nameSpace',
60+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
61+
Cell: (instance: any) => (
62+
<div
63+
style={{
64+
display: 'flex',
65+
justifyContent: 'start',
66+
gap: '0.5rem',
67+
alignItems: 'center',
68+
width: '100%',
69+
cursor: 'pointer',
70+
}}
71+
>
72+
<CopyButton text={instance.cell.value} />
73+
</div>
74+
),
75+
},
76+
],
77+
[],
78+
);
1879
if (error) {
1980
return <IllustratedError error={error} />;
2081
}
@@ -23,52 +84,8 @@ export default function ProjectsList() {
2384
<>
2485
<AnalyticalTable
2586
style={{ margin: '12px' }}
26-
columns={[
27-
{
28-
Header: t('ProjectsListView.title'),
29-
accessor: 'projectName',
30-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
31-
Cell: (instance: any) => (
32-
<div
33-
style={{
34-
cursor: 'pointer',
35-
width: '100%',
36-
color: ThemingParameters.sapLinkColor,
37-
fontWeight: 'bold',
38-
}}
39-
>
40-
{instance.cell.value}
41-
</div>
42-
),
43-
},
44-
{
45-
Header: 'Namespace',
46-
accessor: 'nameSpace',
47-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
48-
Cell: (instance: any) => (
49-
<div
50-
style={{
51-
display: 'flex',
52-
justifyContent: 'start',
53-
gap: '0.5rem',
54-
alignItems: 'center',
55-
width: '100%',
56-
cursor: 'pointer',
57-
}}
58-
>
59-
<CopyButton text={instance.cell.value} />
60-
</div>
61-
),
62-
},
63-
]}
64-
data={
65-
data?.map((e) => {
66-
return {
67-
projectName: e,
68-
nameSpace: projectnameToNamespace(e),
69-
};
70-
}) ?? []
71-
}
87+
columns={stabilizedColumns}
88+
data={stabilizedData}
7289
// eslint-disable-next-line @typescript-eslint/no-explicit-any
7390
onRowClick={(e: any) => {
7491
navigate(

src/components/Yaml/YamlLoader.tsx

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { YamlViewButtonProps } from './YamlViewButton.tsx';
2+
import { FC } from 'react';
3+
4+
import { stringify } from 'yaml';
5+
6+
import { useTranslation } from 'react-i18next';
7+
import { ResourceObject } from '../../lib/api/types/crate/resourceObject.ts';
8+
import Loading from '../Shared/Loading.tsx';
9+
import IllustratedError from '../Shared/IllustratedError.tsx';
10+
import YamlViewer from './YamlViewer.tsx';
11+
import useResource from '../../lib/api/useApiResource';
12+
13+
export const YamlLoader: FC<YamlViewButtonProps> = ({
14+
workspaceName,
15+
resourceType,
16+
resourceName,
17+
}) => {
18+
const { isLoading, data, error } = useResource(
19+
ResourceObject(workspaceName ?? '', resourceType, resourceName),
20+
);
21+
const { t } = useTranslation();
22+
if (isLoading) return <Loading />;
23+
if (error) {
24+
return <IllustratedError error={t('common.cannotLoadData')} />;
25+
}
26+
27+
return (
28+
<YamlViewer
29+
yamlString={stringify(data)}
30+
filename={`${workspaceName ? `${workspaceName}_` : ''}${resourceType}_${resourceName}`}
31+
/>
32+
);
33+
};

0 commit comments

Comments
 (0)