Skip to content

Commit 1bb564c

Browse files
committed
assign/remove principal-roles to catalog-roles
1 parent ce44ebf commit 1bb564c

File tree

7 files changed

+635
-11
lines changed

7 files changed

+635
-11
lines changed
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import {NextRequest, NextResponse} from 'next/server';
2+
import {apiManagementPrincipalRoleCatalogRoleUrl} from "@/app/constants";
3+
import {getRealmHeadersFromRequest} from "@/utils/auth";
4+
5+
export async function DELETE(
6+
request: NextRequest,
7+
{params}: { params: Promise<{ principalRoleName: string; catalogName: string; catalogRoleName: string }> }
8+
) {
9+
try {
10+
const authHeader = request.headers.get('Authorization');
11+
12+
if (!authHeader) {
13+
return NextResponse.json(
14+
{
15+
error: {
16+
message: 'Authorization header is required',
17+
type: 'UnauthorizedError',
18+
code: 401
19+
}
20+
},
21+
{status: 401}
22+
);
23+
}
24+
25+
const {principalRoleName, catalogName, catalogRoleName} = await params;
26+
const url = apiManagementPrincipalRoleCatalogRoleUrl(principalRoleName, catalogName, catalogRoleName);
27+
28+
console.log('Revoking catalog role from principal role:', principalRoleName, catalogName, catalogRoleName);
29+
30+
const realmHeaders = getRealmHeadersFromRequest(request);
31+
32+
const response = await fetch(url, {
33+
method: 'DELETE',
34+
headers: {
35+
'Authorization': authHeader,
36+
'Content-Type': 'application/json',
37+
...realmHeaders,
38+
},
39+
});
40+
41+
if (!response.ok) {
42+
const data = await response.json().catch(() => ({
43+
error: {
44+
message: 'Failed to revoke catalog role',
45+
type: 'RevokeError',
46+
code: response.status
47+
}
48+
}));
49+
console.error('Backend error response:', data);
50+
return NextResponse.json(data, {status: response.status});
51+
}
52+
53+
console.log('Catalog role revoked successfully');
54+
return NextResponse.json({success: true}, {status: 204});
55+
} catch (error) {
56+
console.error('Revoke catalog role proxy error:', error);
57+
return NextResponse.json(
58+
{error: {message: 'Internal server error', type: 'InternalServerError', code: 500}},
59+
{status: 500}
60+
);
61+
}
62+
}
63+
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import {NextRequest, NextResponse} from 'next/server';
2+
import {apiManagementPrincipalRoleCatalogRolesUrl} from "@/app/constants";
3+
import {getRealmHeadersFromRequest} from "@/utils/auth";
4+
5+
export async function PUT(
6+
request: NextRequest,
7+
{params}: { params: Promise<{ principalRoleName: string; catalogName: string }> }
8+
) {
9+
try {
10+
const authHeader = request.headers.get('Authorization');
11+
12+
if (!authHeader) {
13+
return NextResponse.json(
14+
{
15+
error: {
16+
message: 'Authorization header is required',
17+
type: 'UnauthorizedError',
18+
code: 401
19+
}
20+
},
21+
{status: 401}
22+
);
23+
}
24+
25+
const {principalRoleName, catalogName} = await params;
26+
const url = apiManagementPrincipalRoleCatalogRolesUrl(principalRoleName, catalogName);
27+
28+
console.log('Assigning catalog role to principal role:', principalRoleName, catalogName);
29+
30+
const body = await request.json();
31+
console.log('Request body:', body);
32+
33+
const realmHeaders = getRealmHeadersFromRequest(request);
34+
35+
const response = await fetch(url, {
36+
method: 'PUT',
37+
headers: {
38+
'Authorization': authHeader,
39+
'Content-Type': 'application/json',
40+
...realmHeaders,
41+
},
42+
body: JSON.stringify(body),
43+
});
44+
45+
if (!response.ok) {
46+
const data = await response.json().catch(() => ({
47+
error: {
48+
message: 'Failed to assign catalog role',
49+
type: 'AssignError',
50+
code: response.status
51+
}
52+
}));
53+
console.error('Backend error response:', data);
54+
return NextResponse.json(data, {status: response.status});
55+
}
56+
57+
console.log('Catalog role assigned successfully');
58+
return NextResponse.json({success: true}, {status: 201});
59+
} catch (error) {
60+
console.error('Assign catalog role proxy error:', error);
61+
return NextResponse.json(
62+
{error: {message: 'Internal server error', type: 'InternalServerError', code: 500}},
63+
{status: 500}
64+
);
65+
}
66+
}
67+

src/app/catalogs/page.tsx

Lines changed: 90 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,17 @@ import PrincipalRolesList, {PrincipalRoleItem, PrincipalItem} from "@/app/ui/pri
66
import Grants, {Grant} from "@/app/ui/grants"
77
import CreateCatalogModal from "@/app/ui/create-catalog-modal"
88
import CreateCatalogRoleModal from "@/app/ui/create-catalog-role-modal"
9+
import AssignCatalogRoleModal from "@/app/ui/assign-catalog-role-modal"
10+
import RemoveCatalogRoleModal from "@/app/ui/remove-catalog-role-modal"
911
import {useCallback, useEffect, useState} from 'react'
10-
import {Breadcrumb, Button, Divider, Flex, message, Space, Spin, Typography} from 'antd';
11-
import {FolderAddOutlined, FolderOpenOutlined, UsergroupAddOutlined} from '@ant-design/icons';
12+
import {Breadcrumb, Button, Divider, message, Space, Spin, Typography, Flex} from 'antd';
13+
import {
14+
FolderAddOutlined,
15+
FolderOpenOutlined,
16+
MinusOutlined,
17+
PlusOutlined,
18+
UsergroupAddOutlined
19+
} from '@ant-design/icons';
1220
import {useAuthenticatedFetch} from '@/hooks/useAuthenticatedFetch';
1321

1422
const {Title} = Typography;
@@ -39,6 +47,8 @@ export default function Page() {
3947
const [grantsLoading, setGrantsLoading] = useState(false);
4048
const [createModalVisible, setCreateModalVisible] = useState(false);
4149
const [createCatalogRoleModalVisible, setCreateCatalogRoleModalVisible] = useState(false);
50+
const [assignCatalogRoleModalVisible, setAssignCatalogRoleModalVisible] = useState(false);
51+
const [removeCatalogRoleModalVisible, setRemoveCatalogRoleModalVisible] = useState(false);
4252
const {authenticatedFetch} = useAuthenticatedFetch();
4353

4454
const getCatalogs = useCallback(async (): Promise<CatalogEntity[]> => {
@@ -260,10 +270,10 @@ export default function Page() {
260270

261271
try {
262272
await authenticatedFetch(
263-
`/api/principal-roles/${encodeURIComponent(selectedCatalog)}/${encodeURIComponent(selectedCatalogRole)}/${encodeURIComponent(principalRoleName)}`,
264-
{
265-
method: 'DELETE',
266-
}
273+
`/api/principal-roles/${encodeURIComponent(selectedCatalog)}/${encodeURIComponent(selectedCatalogRole)}/${encodeURIComponent(principalRoleName)}`,
274+
{
275+
method: 'DELETE',
276+
}
267277
);
268278

269279
message.success(`Principal role "${principalRoleName}" removed from catalog role "${selectedCatalogRole}" successfully!`);
@@ -280,10 +290,10 @@ export default function Page() {
280290
const handleDeletePrincipal = async (principalRoleName: string, principalName: string) => {
281291
try {
282292
await authenticatedFetch(
283-
`/api/principal-role-principals/${encodeURIComponent(principalRoleName)}/${encodeURIComponent(principalName)}`,
284-
{
285-
method: 'DELETE',
286-
}
293+
`/api/principal-role-principals/${encodeURIComponent(principalRoleName)}/${encodeURIComponent(principalName)}`,
294+
{
295+
method: 'DELETE',
296+
}
287297
);
288298

289299
message.success(`Principal "${principalName}" removed from role "${principalRoleName}" successfully!`);
@@ -366,6 +376,40 @@ export default function Page() {
366376
}
367377
};
368378

379+
const handleAddCatalogRole = () => {
380+
setAssignCatalogRoleModalVisible(true);
381+
};
382+
383+
const handleRemoveCatalogRole = () => {
384+
setRemoveCatalogRoleModalVisible(true);
385+
};
386+
387+
const handleAssignCatalogRoleSuccess = async () => {
388+
if (selectedCatalog && selectedCatalogRole) {
389+
// Refresh the principal roles list
390+
setPrincipalRolesLoading(true);
391+
try {
392+
const roles = await getPrincipalRoles(selectedCatalog, selectedCatalogRole);
393+
setPrincipalRoles(roles);
394+
} finally {
395+
setPrincipalRolesLoading(false);
396+
}
397+
}
398+
};
399+
400+
const handleRemoveCatalogRoleSuccess = async () => {
401+
if (selectedCatalog && selectedCatalogRole) {
402+
// Refresh the principal roles list
403+
setPrincipalRolesLoading(true);
404+
try {
405+
const roles = await getPrincipalRoles(selectedCatalog, selectedCatalogRole);
406+
setPrincipalRoles(roles);
407+
} finally {
408+
setPrincipalRolesLoading(false);
409+
}
410+
}
411+
};
412+
369413
if (loading) {
370414
return (
371415
<Spin size="large"/>
@@ -436,6 +480,25 @@ export default function Page() {
436480
{selectedCatalog && selectedCatalogRole && (
437481
<>
438482
<Divider orientation="left">Principal Roles Assigned to Catalog Role</Divider>
483+
<Flex justify="flex-end" align="center" style={{width: '100%'}}>
484+
<Space>
485+
<Button
486+
type="default"
487+
icon={<PlusOutlined/>}
488+
onClick={handleAddCatalogRole}
489+
>
490+
Add Role
491+
</Button>
492+
<Button
493+
danger
494+
icon={<MinusOutlined/>}
495+
onClick={handleRemoveCatalogRole}
496+
disabled={principalRoles.length === 0}
497+
>
498+
Remove Role
499+
</Button>
500+
</Space>
501+
</Flex>
439502
<PrincipalRolesList
440503
roles={principalRoles}
441504
loading={principalRolesLoading}
@@ -468,6 +531,23 @@ export default function Page() {
468531
onClose={() => setCreateCatalogRoleModalVisible(false)}
469532
onSuccess={handleCreateCatalogRoleSuccess}
470533
/>
534+
535+
<AssignCatalogRoleModal
536+
visible={assignCatalogRoleModalVisible}
537+
catalogName={selectedCatalog}
538+
catalogRoleName={selectedCatalogRole}
539+
onClose={() => setAssignCatalogRoleModalVisible(false)}
540+
onSuccess={handleAssignCatalogRoleSuccess}
541+
/>
542+
543+
<RemoveCatalogRoleModal
544+
visible={removeCatalogRoleModalVisible}
545+
catalogName={selectedCatalog}
546+
catalogRoleName={selectedCatalogRole}
547+
assignedPrincipalRoles={principalRoles}
548+
onClose={() => setRemoveCatalogRoleModalVisible(false)}
549+
onSuccess={handleRemoveCatalogRoleSuccess}
550+
/>
471551
</Space>
472552
)
473553
}

src/app/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ export const apiManagementPrincipalsUrl = apiManagementUrl + "/principals";
77
export const apiManagementPrincipalRolesUrl = apiManagementUrl + "/principal-roles";
88
export const apiManagementPrincipalPrincipalRolesUrl = (principalName: string) => apiManagementUrl + `/principals/${principalName}/principal-roles`;
99
export const apiManagementPrincipalRolePrincipalsUrl = (principalRoleName: string) => apiManagementUrl + `/principal-roles/${principalRoleName}/principals`;
10+
export const apiManagementPrincipalRoleCatalogRolesUrl = (principalRoleName: string, catalogName: string) => apiManagementUrl + `/principal-roles/${principalRoleName}/catalog-roles/${catalogName}`;
11+
export const apiManagementPrincipalRoleCatalogRoleUrl = (principalRoleName: string, catalogName: string, catalogRoleName: string) => apiManagementUrl + `/principal-roles/${principalRoleName}/catalog-roles/${catalogName}/${catalogRoleName}`;
1012

1113
export const apiCatalogUrl = process.env.POLARIS_CATALOG_API_URL || "http://localhost:8181/api/catalog/v1";
1214
export const apiCatalogAuthUrl = apiCatalogUrl + "/oauth/tokens"

0 commit comments

Comments
 (0)