Skip to content

Commit 3924a60

Browse files
Configs table
providers config table
1 parent 09350c5 commit 3924a60

File tree

8 files changed

+248
-25
lines changed

8 files changed

+248
-25
lines changed

public/locales/en.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@
2727
"tableHeaderSynced": "Synced",
2828
"tableHeaderReady": "Ready"
2929
},
30+
"ProvidersConfig": {
31+
"headerProviderConfigs": "Provider Configs",
32+
"tableHeaderProvider": "Provider",
33+
"tableHeaderName": "Name",
34+
"tableHeaderCreated": "Created",
35+
"tableHeaderUsage": "Usage"
36+
},
3037
"ControlPlaneListToolbar": {
3138
"buttonText": "Workspace"
3239
},
@@ -150,9 +157,6 @@
150157
"tableHeaderInstalled": "Installed",
151158
"tableHeaderHealthy": "Healthy"
152159
},
153-
"ProvidersConfig": {
154-
"header": "Provider Configs"
155-
},
156160
"validationErrors": {
157161
"required": "This field is required!",
158162
"properFormatting": "Use A-Z, a-z, 0-9, hyphen (-), and period (.), but note that whitespace (spaces, tabs, etc.) is not allowed for proper compatibility.",

src/components/ControlPlane/ProvidersConfig.tsx

Lines changed: 52 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,23 +3,69 @@ import { useTranslation } from 'react-i18next';
33
import { AnalyticalTable, AnalyticalTableColumnDefinition, AnalyticalTableScaleWidthMode, Title } from '@ui5/webcomponents-react';
44
import '@ui5/webcomponents-icons/dist/sys-enter-2';
55
import '@ui5/webcomponents-icons/dist/sys-cancel-2';
6+
import { useProvidersConfigResource } from '../../lib/api/useApiResource';
7+
import { timeAgo } from '../../utils/i18n/timeAgo';
8+
9+
type Rows = {
10+
parent: string;
11+
name: string;
12+
usage: string;
13+
created: string;
14+
};
615

7-
//empty table TBD
816
export function ProvidersConfig() {
917
const { t } = useTranslation();
18+
const rows: Rows[] = [];
19+
20+
const {
21+
data: providerConfigsList,
22+
isLoading
23+
} = useProvidersConfigResource({
24+
refreshInterval: 60000, // Resources are quite expensive to fetch, so we refresh every 60 seconds
25+
});
1026

11-
const columns: AnalyticalTableColumnDefinition[] = [];
27+
if (providerConfigsList) {
28+
providerConfigsList.forEach(provider => {
29+
provider.items.forEach(config => {
30+
rows.push({
31+
parent: provider.provider,
32+
name: config.metadata.name,
33+
usage: config.metadata.usage ? config.metadata.usage : "0",
34+
created: timeAgo.format(new Date(config.metadata.creationTimestamp)),
35+
})
36+
});
37+
})
38+
}
39+
40+
const columns: AnalyticalTableColumnDefinition[] = [
41+
{
42+
Header: t('ProvidersConfig.tableHeaderProvider'),
43+
accessor: 'parent',
44+
},
45+
{
46+
Header: t('ProvidersConfig.tableHeaderName'),
47+
accessor: 'name',
48+
},
49+
{
50+
Header: t('ProvidersConfig.tableHeaderUsage'),
51+
accessor: 'usage',
52+
},
53+
{
54+
Header: t('ProvidersConfig.tableHeaderCreated'),
55+
accessor: 'created',
56+
}
57+
];
1258

1359
return (
1460
<>
15-
<Title level='H4'>{t('ProvidersConfig.header')}</Title>
61+
<Title level='H4'>{t('ProvidersConfig.headerProviderConfigs')}</Title>
1662
<AnalyticalTable
1763
columns={columns}
18-
data={[]}
64+
data={rows ?? []}
1965
minRows={1}
20-
groupBy={['name']}
66+
groupBy={['parent']}
2167
scaleWidthMode={AnalyticalTableScaleWidthMode.Smart}
22-
loading={false}
68+
loading={isLoading}
2369
filterable
2470
// Prevent the table from resetting when the data changes
2571
retainColumnWidth

src/components/ControlPlane/ProvidersList.tsx

Lines changed: 0 additions & 13 deletions
This file was deleted.

src/index.css

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@
9494
background-color: #FFC933;
9595
}
9696

97+
.crossplane-table-element {
98+
margin-bottom: 25px;
99+
}
100+
97101
.cp-panel-gitops {
98102
background-color: #D1EFFF;
99103
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { Resource } from '../resource';
2+
3+
export type CRDResponse =
4+
{
5+
items: [{
6+
metadata: {
7+
name: string;
8+
creationTimestamp: string;
9+
};
10+
status: {
11+
conditions: [{
12+
type: "Ready" | "Synced" | unknown;
13+
status: "True" | "False";
14+
lastTransitionTime: string;
15+
}]
16+
};
17+
spec: {
18+
names: {
19+
kind: string;
20+
},
21+
versions: [{
22+
name: string;
23+
}],
24+
group: string;
25+
}
26+
}];
27+
}
28+
;
29+
30+
export const CRDRequest: Resource<CRDResponse> = {
31+
path: '/apis/apiextensions.k8s.io/v1/customresourcedefinitions',
32+
};

src/lib/api/useApiResource.ts

Lines changed: 116 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { useContext } from 'react';
1+
import { useContext, useEffect, useState } from 'react';
22
import useSWR, { SWRConfiguration, useSWRConfig } from 'swr';
33
import { fetchApiServerJson } from './fetch';
44
import { ApiConfigContext } from '../../components/Shared/k8s';
@@ -7,6 +7,8 @@ import { ApiConfig } from './types/apiConfig';
77
import { Resource } from './types/resource';
88
import useSWRMutation, { SWRMutationConfiguration } from 'swr/mutation';
99
import { MutatorOptions } from 'swr/_internal';
10+
import { CRDRequest, CRDResponse } from './types/crossplane/CRDList';
11+
import { ProviderConfigs, ProviderConfigsData, ProviderConfigsDataForRequest } from '../shared/types';
1012

1113
export { useApiResource as default };
1214

@@ -39,6 +41,119 @@ export const useApiResource = <T>(
3941
};
4042
};
4143

44+
export const useProvidersConfigResource = (
45+
config?: SWRConfiguration,
46+
) => {
47+
const apiConfig = useContext(ApiConfigContext);
48+
const { data, error, isValidating } = useSWR(
49+
CRDRequest.path === null
50+
? null //TODO: is null a valid key?
51+
: [CRDRequest.path, apiConfig],
52+
([path, apiConfig]) =>
53+
fetchApiServerJson<CRDResponse>(
54+
path,
55+
apiConfig,
56+
CRDRequest.jq,
57+
CRDRequest.method,
58+
CRDRequest.body,
59+
),
60+
config,
61+
);
62+
63+
let providerConfigsDataForRequest: ProviderConfigsDataForRequest[] = [];
64+
65+
const crdWithProviderConfig = data?.items.filter(x => x.spec.names.kind === "ProviderConfig");
66+
67+
const providerConfigsData: ProviderConfigsData[] = crdWithProviderConfig?.map((item) => {
68+
return {
69+
provider: item.metadata.name,
70+
name: item.spec.group,
71+
versions: item.spec.versions,
72+
}
73+
}) ?? [];
74+
75+
if (providerConfigsData.length > 0) {
76+
providerConfigsData.forEach(item => {
77+
item.versions.forEach(version => {
78+
providerConfigsDataForRequest.push({
79+
provider: item.provider,
80+
url: item.name,
81+
version: version.name
82+
})
83+
})
84+
});
85+
}
86+
87+
providerConfigsDataForRequest.forEach(async item => {
88+
const data =
89+
await fetchApiServerJson<ProviderConfigs>(
90+
`/apis/${item.url ?? ""}/${item.version}/providerconfigs`,
91+
apiConfig,
92+
CRDRequest.jq,
93+
CRDRequest.method,
94+
CRDRequest.body,
95+
)
96+
if (data) {
97+
providerConfigs.push(data);
98+
}
99+
})
100+
101+
const providerConfigs: ProviderConfigs[] = [];
102+
103+
const fetchProviderConfigs = async () => {
104+
try {
105+
// Create an array of promises for each fetch call
106+
const fetchPromises = providerConfigsDataForRequest.map(async (item) => {
107+
const data = await fetchApiServerJson<ProviderConfigs>(
108+
`/apis/${item.url ?? ""}/${item.version}/providerconfigs`,
109+
apiConfig,
110+
CRDRequest.jq,
111+
CRDRequest.method,
112+
CRDRequest.body
113+
);
114+
data.provider = item.provider
115+
return data; // Return fetched data
116+
});
117+
118+
// Wait for all fetch operations to complete
119+
const providerConfigs = await Promise.all(fetchPromises);
120+
121+
// Filter out any null/undefined values and return the valid data
122+
return providerConfigs.filter(config => config !== null);
123+
} catch (error) {
124+
console.error('Error fetching provider configs:', error);
125+
return []; // Return an empty array in case of error
126+
}
127+
};
128+
const [configs, setConfigs] = useState<ProviderConfigs[]>([]);
129+
const [isLoading, setIsLoading] = useState(true);
130+
131+
useEffect(() => {
132+
const fetchDataAndUpdateState = async () => {
133+
setIsLoading(true);
134+
try {
135+
const finalData = await fetchProviderConfigs();
136+
137+
setConfigs(finalData);
138+
if (finalData.length > 0) {
139+
setIsLoading(false);
140+
}
141+
} catch (err) {
142+
setIsLoading(false);
143+
}
144+
};
145+
146+
fetchDataAndUpdateState();
147+
}, [data]);
148+
149+
return {
150+
data: configs,
151+
error: error as APIError,
152+
isLoading: isLoading,
153+
isValidating: isValidating,
154+
};
155+
}
156+
42157
export const useApiResourceMutation = <T>(
43158
resource: Resource<T>,
44159
config?: SWRMutationConfiguration<T, any, any, any>,

src/lib/shared/types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
export type ProviderConfigsData = {
2+
provider: string,
3+
name: string,
4+
versions: [{
5+
name: string
6+
}]
7+
}
8+
9+
export type ProviderConfigsDataForRequest = {
10+
provider: string,
11+
url: string,
12+
version: string
13+
}
14+
15+
export type ProviderConfigs = {
16+
provider: string,
17+
items: [
18+
{
19+
kind: string;
20+
metadata: {
21+
provider: string;
22+
name: string;
23+
usage: string;
24+
creationTimestamp: string;
25+
};
26+
status: {
27+
count: string;
28+
};
29+
},
30+
];
31+
}

src/views/ControlPlanes/ControlPlaneView.tsx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,15 @@ import {
1717
McpContextProvider,
1818
WithinManagedControlPlane,
1919
} from '../../lib/shared/McpContext.tsx';
20-
import ProvidersList from '../../components/ControlPlane/ProvidersList.tsx';
2120
import FluxList from '../../components/ControlPlane/FluxList.tsx';
2221
import { ControlPlane as ControlPlaneResource } from '../../lib/api/types/crate/controlPlanes.ts';
2322
import useResource from '../../lib/api/useApiResource.ts';
2423
import MCPHealthPopoverButton from '../../components/ControlPlane/MCPHealthPopoverButton.tsx';
2524
import ComponentList from '../../components/ControlPlane/ComponentList.tsx';
2625
import { useTranslation } from 'react-i18next';
26+
import { ManagedResources } from '../../components/ControlPlane/ManagedResources.tsx';
27+
import { Providers } from '../../components/ControlPlane/Providers.tsx';
28+
import { ProvidersConfig } from '../../components/ControlPlane/ProvidersConfig.tsx';
2729

2830
export default function ControlPlaneView() {
2931
const { projectName, workspaceName, controlPlaneName, contextName } =
@@ -122,7 +124,9 @@ export default function ControlPlaneView() {
122124
}
123125
noAnimation
124126
>
125-
<ProvidersList />
127+
<div className='crossplane-table-element'><Providers /></div>
128+
<div className='crossplane-table-element'><ProvidersConfig /></div>
129+
<div className='crossplane-table-element'><ManagedResources /></div>
126130
</Panel>
127131
</ObjectPageSection>
128132
<ObjectPageSection

0 commit comments

Comments
 (0)