Skip to content
This repository was archived by the owner on Feb 20, 2024. It is now read-only.

Commit a62dee6

Browse files
Merge pull request #29 from solace-iot-team/refactor-orgs
Refactor orgs
2 parents 1b7fe3c + 47ea946 commit a62dee6

File tree

106 files changed

+9365
-1132
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

106 files changed

+9365
-1132
lines changed

ReleaseNotes.md

+39
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,45 @@
22

33
Solace Async API Management.
44

5+
## Version 0.1.9
6+
* [API-M Admin & Developer Portal](https://github.com/solace-iot-team/async-apim/tree/main/apim-portal): 0.1.9
7+
* [API-M Server OpenAPI](https://github.com/solace-iot-team/async-apim/blob/main/apim-server/server/common/api.yml): 0.1.1
8+
* [API-M Server](https://github.com/solace-iot-team/async-apim/tree/main/apim-server): 0.1.1
9+
* [API-M Connector OpenAPI](https://github.com/solace-iot-team/platform-api): 0.7.11
10+
11+
#### API-M Admin & Developer Portal
12+
13+
**Enhancements:**
14+
* **Admin-Portal:System:Manage Orgs & Admin-Portal:Organization:Settings**
15+
- refactored list, view, create & edit forms
16+
- added Integration section with Notification Hub Configuration
17+
* **Admin-Portal:Organization:Settings**
18+
- added number of APIs per API Product restriction possibility
19+
- added App credentials expiry duration
20+
* **Admin-Portal:System:Manage Orgs: Import Orgs**
21+
- allows for importing Connector Orgs to APIM Portal
22+
* **Admin & Developer Portal: Apps**
23+
- added using the configured appCredentialsExpiryDuration for the organization
24+
* **Admin Portal: API Products**
25+
- control number of apis per api product using the configured maxNumApisPerApiProduct for the organization
26+
27+
**New Features:**
28+
* **Business Group Roles**
29+
- added Business Group roles in preparation to extend/enhance RBAC in the future
30+
- role=businessGroupAdmin
31+
- role=apiViewer
32+
33+
**Retired:**
34+
* **Role: loginAs**
35+
- removed role loginAs from list of roles
36+
* **Bootstrap: Users**
37+
- removed bootstrap users feature
38+
39+
#### API-M Server / OpenAPI
40+
41+
* **Organization**
42+
- added maxNumApisPerApiProduct and appCredentialsExpiryDuration properties to Organzation
43+
544
## Version 0.1.8
645
* [API-M Admin & Developer Portal](https://github.com/solace-iot-team/async-apim/tree/main/apim-portal): 0.1.8
746
* [API-M Server OpenAPI](https://github.com/solace-iot-team/async-apim/blob/main/apim-server/server/common/api.yml): 0.1.0

apim-portal/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "async-apim-portal",
3-
"version": "0.1.8",
3+
"version": "0.1.9",
44
"description": "Solace Async API Management Portal",
55
"repository": {
66
"type": "git",

apim-portal/src/admin-portal/AdminPortalAppRoutes.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,12 @@ import { ManageSystemUsersPage } from "./pages/ManageSystemUsersPage";
2525
import { ManageSystemOrganizationsPage } from "./pages/ManageSystemOrganizationsPage";
2626
import { ManageApiProductsPage } from "./pages/ManageApiProductsPage";
2727

28+
// DELETEME
2829
import { ManageApiProductsPage as deleteme_ManageApiProductsPage} from "./pages/deleteme.ManageApiProductsPage";
2930
import { ManageAppsPage as deleteme_ManageAppsPage} from './pages/deleteme.ManageAppsPage';
30-
31+
import { ManageSystemOrganizationsPage as deleteme_ManageSystemOrganizationsPage } from "./pages/deleteme_ManageSystemOrganizationsPage";
32+
import { MonitorOrganizationStatusPage as deleteme_MonitorOrganizationStatusPage } from "./pages/deleteme_MonitorOrganizationStatusPage";
33+
import { ManageOrganizationSettingsPage as deleteme_ManageOrganizationSettingsPage } from "./pages/deleteme_ManageOrganizationSettingsPage";
3134

3235

3336

@@ -43,10 +46,18 @@ export const AdminPortalAppRoutes = (): Array<JSX.Element> => {
4346
<ProtectedRouteWithRbac path={EUIAdminPortalResourcePaths.ManageSystemConfigSettings} component={ManageSystemSettingsPage} exact key={EUIAdminPortalResourcePaths.ManageSystemConfigSettings} />,
4447
<ProtectedRouteWithRbac path={EUIAdminPortalResourcePaths.ManageSystemUsers} component={ManageSystemUsersPage} exact key={EUIAdminPortalResourcePaths.ManageSystemUsers} />,
4548
<ProtectedRouteWithRbac path={EUIAdminPortalResourcePaths.ManageSystemOrganizations} component={ManageSystemOrganizationsPage} exact key={EUIAdminPortalResourcePaths.ManageSystemOrganizations} />,
49+
50+
<ProtectedRouteWithRbac path={EUIAdminPortalResourcePaths.deleteme_ManageSystemOrganizations} component={deleteme_ManageSystemOrganizationsPage} exact key={EUIAdminPortalResourcePaths.deleteme_ManageSystemOrganizations} />,
51+
4652
<ProtectedRouteWithRbac path={EUIAdminPortalResourcePaths.MonitorSystemHealth} component={MonitorSystemHealthPage} exact key={EUIAdminPortalResourcePaths.MonitorSystemHealth} />,
4753
/* Organization */
4854
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.ManageOrganizationSettings} component={ManageOrganizationSettingsPage} exact key={EUIAdminPortalResourcePaths.ManageOrganizationSettings} />,
4955
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.MonitorOrganizationStatus} component={MonitorOrganizationStatusPage} exact key={EUIAdminPortalResourcePaths.MonitorOrganizationStatus} />,
56+
57+
// DELETEME
58+
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.deleteme_ManageOrganizationSettings} component={deleteme_ManageOrganizationSettingsPage} exact key={EUIAdminPortalResourcePaths.deleteme_ManageOrganizationSettings} />,
59+
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.deleteme_MonitorOrganizationStatus} component={deleteme_MonitorOrganizationStatusPage} exact key={EUIAdminPortalResourcePaths.deleteme_MonitorOrganizationStatus} />,
60+
5061
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.ManageOrganizationUsers} component={ManageOrgUsersPage} exact key={EUIAdminPortalResourcePaths.ManageOrganizationUsers} />,
5162
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.ManageOrganizationBusinessGroups} component={ManageOrgBusinessGroupsPage} exact key={EUIAdminPortalResourcePaths.ManageOrganizationBusinessGroups} />,
5263
<ProtectedRouteWithRbacAndOrgAccess path={EUIAdminPortalResourcePaths.ManageOrganizationEnvironments} component={ManageOrgEnvironmentsPage} exact key={EUIAdminPortalResourcePaths.ManageOrganizationEnvironments} />,

apim-portal/src/admin-portal/components/AdminPortalSideBar/AdminPortalSideBar.tsx

+15-5
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,16 @@ export const AdminPortalSideBar: React.FC<IAdminPortalSideBarProps> = (props: IA
148148
disabled: isDisabledWithConnectorUnavailable(isDisabledWithoutOrg, EUIAdminPortalResourcePaths.MonitorOrganizationStatus),
149149
command: () => { navigateTo(EUIAdminPortalResourcePaths.MonitorOrganizationStatus); }
150150
},
151+
// {
152+
// label: 'DELETEME: Settings',
153+
// disabled: isDisabledWithConnectorUnavailable(isDisabledWithoutOrg, EUIAdminPortalResourcePaths.deleteme_ManageOrganizationSettings),
154+
// command: () => { navigateTo(EUIAdminPortalResourcePaths.deleteme_ManageOrganizationSettings); }
155+
// },
156+
// {
157+
// label: 'DELETEME: Status',
158+
// disabled: isDisabledWithConnectorUnavailable(isDisabledWithoutOrg, EUIAdminPortalResourcePaths.deleteme_MonitorOrganizationStatus),
159+
// command: () => { navigateTo(EUIAdminPortalResourcePaths.deleteme_MonitorOrganizationStatus); }
160+
// },
151161
{
152162
label: 'Integration',
153163
disabled: isDisabledWithConnectorUnavailable(isDisabledWithoutOrg, EUIAdminPortalResourcePaths.ManageOrganizationIntegration),
@@ -180,6 +190,11 @@ export const AdminPortalSideBar: React.FC<IAdminPortalSideBarProps> = (props: IA
180190
disabled: isDisabledWithConnectorUnavailable(isDisabled, EUIAdminPortalResourcePaths.ManageSystemOrganizations),
181191
command: () => { navigateTo(EUIAdminPortalResourcePaths.ManageSystemOrganizations); }
182192
},
193+
// {
194+
// label: 'DELETEME: Organizations',
195+
// disabled: isDisabledWithConnectorUnavailable(isDisabled, EUIAdminPortalResourcePaths.deleteme_ManageSystemOrganizations),
196+
// command: () => { navigateTo(EUIAdminPortalResourcePaths.deleteme_ManageSystemOrganizations); }
197+
// },
183198
{
184199
label: 'Setup',
185200
items: [
@@ -188,11 +203,6 @@ export const AdminPortalSideBar: React.FC<IAdminPortalSideBarProps> = (props: IA
188203
disabled: isDisabled(EUIAdminPortalResourcePaths.ManageSystemConfigConnectors),
189204
command: () => { navigateTo(EUIAdminPortalResourcePaths.ManageSystemConfigConnectors); }
190205
},
191-
// {
192-
// label: 'Settings',
193-
// disabled: isDisabled(EUIAdminPortalResourcePaths.ManageSystemConfigSettings),
194-
// command: () => { navigateTo(EUIAdminPortalResourcePaths.ManageSystemConfigSettings); }
195-
// }
196206
]
197207
},
198208
{

apim-portal/src/admin-portal/components/ManageApiProducts/EditNewApiProduct/SearchSelectApis.tsx

+136-47
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ import {
1717
TAPApiDisplayList
1818
} from "../../../../displayServices/APApisDisplayService";
1919
import APAdminPortalApisDisplayService from "../../../displayServices/APAdminPortalApisDisplayService";
20+
import { OrganizationContext } from "../../../../components/APContextProviders/APOrganizationContextProvider";
21+
import APOrganizationsDisplayService from "../../../../displayServices/APOrganizationsDisplayService/APOrganizationsDisplayService";
2022

2123
import '../../../../components/APComponents.css';
2224
import "../ManageApiProducts.css";
@@ -36,14 +38,21 @@ export const SearchSelectApis: React.FC<ISearchSelectApisProps> = (props: ISearc
3638
type TManagedObject = TAPApiDisplay;
3739
type TManagedObjectList = Array<TManagedObject>;
3840

39-
const DialogHeader = 'Search & Select API(s):';
41+
const DialogHeaderPlural = 'Search & Select API(s):';
42+
const DialogHeaderSingular = 'Search & Select one API:';
4043
const MessageNoManagedObjectsFound = "No APIs found."
4144
const MessageNoManagedObjectsFoundWithFilter = 'No APIs found for filter';
4245
// const GlobalSearchPlaceholder = 'Enter search word list separated by <space> ...';
4346
const GlobalSearchPlaceholder = 'search...';
4447

48+
const [organizationContext] = React.useContext(OrganizationContext);
49+
50+
const isSingleSelection: boolean = organizationContext.apMaxNumApis_Per_ApiProduct === 1;
51+
52+
const [isMaxExceeded, setIsMaxExceeded] = React.useState<boolean>(false);
4553
const [managedObjectList, setManagedObjectList] = React.useState<TManagedObjectList>();
4654
const [selectedManagedObjectList, setSelectedManagedObjectList] = React.useState<TManagedObjectList>();
55+
const [selectedManagedObject, setSelectedManagedObject] = React.useState<TManagedObject>();
4756
const [isInitialialized, setIsInitialized] = React.useState<boolean>(false);
4857
const [apiCallStatus, setApiCallStatus] = React.useState<TApiCallState | null>(null);
4958
const [globalFilter, setGlobalFilter] = React.useState<string>(); // * Data Table *
@@ -79,16 +88,18 @@ export const SearchSelectApis: React.FC<ISearchSelectApisProps> = (props: ISearc
7988

8089
React.useEffect(() => {
8190
if(managedObjectList === undefined) return;
82-
setSelectedManagedObjectList(APEntityIdsService.create_ApDisplayObjectList_FilteredBy_EntityIdList({
91+
const selectedList: TAPApiDisplayList = APEntityIdsService.create_ApDisplayObjectList_FilteredBy_EntityIdList({
8392
apDisplayObjectList: managedObjectList,
8493
filterByEntityIdList: props.selectedApiEntityIdList
85-
}));
94+
});
95+
if(isSingleSelection && selectedList.length > 0) setSelectedManagedObject(selectedList[0]);
96+
setSelectedManagedObjectList(selectedList);
8697
}, [managedObjectList]); /* eslint-disable-line react-hooks/exhaustive-deps */
8798

8899
React.useEffect(() => {
89100
if(selectedManagedObjectList === undefined) return;
90101
setIsInitialized(true);
91-
}, [selectedManagedObjectList]); /* eslint-disable-line react-hooks/exhaustive-deps */
102+
}, [selectedManagedObjectList, selectedManagedObject]); /* eslint-disable-line react-hooks/exhaustive-deps */
92103

93104
React.useEffect(() => {
94105
if (apiCallStatus !== null) {
@@ -101,8 +112,13 @@ export const SearchSelectApis: React.FC<ISearchSelectApisProps> = (props: ISearc
101112
const onSaveSelectedApis = () => {
102113
const funcName = 'onSaveSelectedApis';
103114
const logName = `${ComponentName}.${funcName}()`;
104-
if(selectedManagedObjectList === undefined) throw new Error(`${logName}: selectedManagedObjectList === undefined`);
105-
props.onSave(selectedManagedObjectList);
115+
if(isSingleSelection) {
116+
if(selectedManagedObject === undefined) throw new Error(`${logName}: isSingleSelection && selectedManagedObject === undefined`);
117+
props.onSave([selectedManagedObject]);
118+
} else {
119+
if(selectedManagedObjectList === undefined) throw new Error(`${logName}: selectedManagedObjectList === undefined`);
120+
props.onSave(selectedManagedObjectList);
121+
}
106122
}
107123

108124
// * Data Table *
@@ -115,7 +131,8 @@ export const SearchSelectApis: React.FC<ISearchSelectApisProps> = (props: ISearc
115131
const funcName = 'renderDataTableHeader';
116132
const logName = `${ComponentName}.${funcName}()`;
117133
if(selectedManagedObjectList === undefined) throw new Error(`${logName}: selectedManagedObjectList === undefined`);
118-
const isSaveDisabled: boolean = selectedManagedObjectList.length === 0;
134+
// const isSaveDisabled: boolean = selectedManagedObjectList.length === 0;
135+
const isSaveDisabled: boolean = isSingleSelection ? selectedManagedObject === undefined : selectedManagedObjectList.length === 0;
119136
return (
120137
<div className="table-header">
121138
<div style={{ whiteSpace: "nowrap"}}>
@@ -137,69 +154,141 @@ export const SearchSelectApis: React.FC<ISearchSelectApisProps> = (props: ISearc
137154
);
138155
}
139156

140-
const onSelectionChange = (event: any): void => {
141-
setSelectedManagedObjectList(event.value);
157+
const onListSelectionChange = (event: any): void => {
158+
const moList: TManagedObjectList = event.value;
159+
if(APOrganizationsDisplayService.is_NumApis_Per_ApiProduct_Limited(organizationContext.apMaxNumApis_Per_ApiProduct)) {
160+
if(moList.length > organizationContext.apMaxNumApis_Per_ApiProduct) setIsMaxExceeded(true);
161+
else {
162+
setIsMaxExceeded(false);
163+
setSelectedManagedObjectList(event.value);
164+
}
165+
} else {
166+
setIsMaxExceeded(false);
167+
setSelectedManagedObjectList(event.value);
168+
}
169+
}
170+
171+
const onSingleSelectionChange = (event: any): void => {
172+
setSelectedManagedObject(event.value);
142173
}
143174

144175
const renderManagedObjectTableEmptyMessage = () => {
145176
if(globalFilter && globalFilter !== '') return `${MessageNoManagedObjectsFoundWithFilter}: ${globalFilter}.`;
146177
else return MessageNoManagedObjectsFound;
147178
}
148179

149-
const renderManagedObjectDataTable = (): JSX.Element => {
180+
const renderManagedObjectDataTableMultiple = (): JSX.Element => {
150181
const dataKey = APAdminPortalApisDisplayService.nameOf_ApEntityId('id');
151182
const sortField = APAdminPortalApisDisplayService.nameOf_ApEntityId('displayName');
152183
return (
153-
<div className="card">
154-
<DataTable
155-
ref={dt}
156-
className="p-datatable-sm"
157-
autoLayout={true}
158-
resizableColumns
159-
columnResizeMode="fit"
160-
showGridlines={false}
161-
header={renderDataTableHeader()}
162-
value={managedObjectList}
163-
globalFilter={globalFilter}
164-
scrollable
165-
scrollHeight="800px"
166-
dataKey={dataKey}
167-
emptyMessage={renderManagedObjectTableEmptyMessage()}
168-
// selection
169-
selection={selectedManagedObjectList}
170-
onSelectionChange={onSelectionChange}
171-
// sorting
172-
sortMode='single'
173-
sortField={sortField}
174-
sortOrder={1}
175-
>
176-
<Column selectionMode="multiple" style={{width:'3em'}}/>
177-
<Column header="Name" field={sortField} filterField="apSearchContent" sortable />
178-
<Column header="Service Name" field="connectorEnvironmentResponse.serviceName" sortable />
179-
<Column header="Msg Vpn Name" field="connectorEnvironmentResponse.msgVpnName" sortable />
180-
<Column header="Datacenter Provider" field="connectorEnvironmentResponse.datacenterProvider" sortable />
181-
{/* <Column header="Description" field="connectorEnvironmentResponse.description" /> */}
184+
<div className="card p-mt-2">
185+
<DataTable
186+
ref={dt}
187+
className="p-datatable-sm"
188+
autoLayout={true}
189+
resizableColumns
190+
columnResizeMode="fit"
191+
showGridlines={false}
192+
header={renderDataTableHeader()}
193+
value={managedObjectList}
194+
globalFilter={globalFilter}
195+
scrollable
196+
scrollHeight="800px"
197+
dataKey={dataKey}
198+
emptyMessage={renderManagedObjectTableEmptyMessage()}
199+
// selection
200+
selection={selectedManagedObjectList}
201+
onSelectionChange={onListSelectionChange}
202+
// sorting
203+
sortMode='single'
204+
sortField={sortField}
205+
sortOrder={1}
206+
>
207+
<Column selectionMode="multiple" style={{width:'3em'}}/>
208+
<Column header="Name" field={sortField} filterField="apSearchContent" sortable />
209+
<Column header="Service Name" field="connectorEnvironmentResponse.serviceName" sortable />
210+
<Column header="Msg Vpn Name" field="connectorEnvironmentResponse.msgVpnName" sortable />
211+
<Column header="Datacenter Provider" field="connectorEnvironmentResponse.datacenterProvider" sortable />
212+
{/* <Column header="Description" field="connectorEnvironmentResponse.description" /> */}
182213
</DataTable>
183214
</div>
184215
);
185216
}
186217

218+
const renderManagedObjectDataTableSingle = (): JSX.Element => {
219+
const dataKey = APAdminPortalApisDisplayService.nameOf_ApEntityId('id');
220+
const sortField = APAdminPortalApisDisplayService.nameOf_ApEntityId('displayName');
221+
return (
222+
<div className="card p-mt-2">
223+
<DataTable
224+
ref={dt}
225+
className="p-datatable-sm"
226+
autoLayout={true}
227+
resizableColumns
228+
columnResizeMode="fit"
229+
showGridlines={false}
230+
header={renderDataTableHeader()}
231+
value={managedObjectList}
232+
globalFilter={globalFilter}
233+
scrollable
234+
scrollHeight="800px"
235+
dataKey={dataKey}
236+
emptyMessage={renderManagedObjectTableEmptyMessage()}
237+
// selection
238+
selectionMode="single"
239+
selection={selectedManagedObject}
240+
onSelectionChange={onSingleSelectionChange}
241+
// sorting
242+
sortMode='single'
243+
sortField={sortField}
244+
sortOrder={1}
245+
>
246+
<Column header="Name" field={sortField} filterField="apSearchContent" sortable />
247+
<Column header="Service Name" field="connectorEnvironmentResponse.serviceName" sortable />
248+
<Column header="Msg Vpn Name" field="connectorEnvironmentResponse.msgVpnName" sortable />
249+
<Column header="Datacenter Provider" field="connectorEnvironmentResponse.datacenterProvider" sortable />
250+
{/* <Column header="Description" field="connectorEnvironmentResponse.description" /> */}
251+
</DataTable>
252+
</div>
253+
);
254+
}
255+
256+
const renderManagedObjectDataTable = (): JSX.Element => {
257+
if(isSingleSelection) return renderManagedObjectDataTableSingle();
258+
else return renderManagedObjectDataTableMultiple();
259+
}
260+
261+
const renderHeader = () => {
262+
if(isSingleSelection) {
263+
return (
264+
<APComponentHeader header={DialogHeaderSingular} />
265+
);
266+
} else {
267+
return (
268+
<APComponentHeader header={DialogHeaderPlural} />
269+
);
270+
}
271+
}
272+
273+
const renderMaxExceededMessage = () => {
274+
return(
275+
<div style={{ color: 'red' }}>
276+
Max number of APIs per API Product exceeded. Max: {organizationContext.apMaxNumApis_Per_ApiProduct}.
277+
</div>
278+
)
279+
}
280+
187281
return (
188282
<div className="manage-api-products">
189283

190-
<APComponentHeader header={DialogHeader} />
284+
{ renderHeader() }
285+
286+
{ isMaxExceeded && renderMaxExceededMessage() }
191287

192288
<ApiCallStatusError apiCallStatus={apiCallStatus} />
193289

194290
{ isInitialialized && renderManagedObjectDataTable() }
195291

196-
{/* DEBUG */}
197-
{/* {managedObjectTableDataList.length > 0 && selectedManagedObjectTableDataList &&
198-
<pre style={ { fontSize: '12px' }} >
199-
{JSON.stringify(selectedManagedObjectTableDataList, null, 2)}
200-
</pre>
201-
} */}
202-
203292
</div>
204293
);
205294
}

0 commit comments

Comments
 (0)