Skip to content

Commit a0e8920

Browse files
Merge pull request #1910 from redpanda-data/front-end/show-errors-acls
front-end: show errors on create/update acls
2 parents 9aa7cc3 + 33f1c5e commit a0e8920

File tree

6 files changed

+54
-28
lines changed

6 files changed

+54
-28
lines changed

frontend/src/components/pages/acls/new-acl/ACL.model.tsx

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
type ListACLsResponse,
88
} from 'protogen/redpanda/api/dataplane/v1/acl_pb';
99
import type { ReactNode } from 'react';
10+
import type { ConnectError } from '@connectrpc/connect';
11+
import type { UseToastOptions } from '@redpanda-data/ui';
1012

1113
export type OperationType = 'not-set' | 'allow' | 'deny';
1214
export const OperationTypeNotSet: OperationType = 'not-set';
@@ -546,3 +548,27 @@ export function calculateACLDifference(currentRules: ACLWithId[], newRules: ACLW
546548
toDelete,
547549
};
548550
}
551+
552+
export const handleResponses = (toast: (op: UseToastOptions) => void, errors: ConnectError[], created: boolean) => {
553+
if (errors.length > 0 && created) {
554+
errors.forEach((er) => {
555+
toast({
556+
status: 'warning',
557+
title: 'Some ACLs were created, but there were errors',
558+
description: er.message,
559+
});
560+
});
561+
} else if (errors.length > 0 && !created) {
562+
errors.forEach((er) => {
563+
toast({
564+
status: 'error',
565+
description: er.message,
566+
});
567+
});
568+
} else {
569+
toast({
570+
status: 'success',
571+
description: 'ACLs created successfully',
572+
});
573+
}
574+
};

frontend/src/components/pages/acls/new-acl/AclCreatePage.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
PrincipalTypeUser,
1616
parsePrincipal,
1717
type Rule,
18+
handleResponses,
1819
} from 'components/pages/acls/new-acl/ACL.model';
1920
import CreateACL from 'components/pages/acls/new-acl/CreateACL';
2021
import { useEffect } from 'react';
@@ -39,12 +40,8 @@ const AclCreatePage = () => {
3940

4041
const createAclMutation = async (principal: string, host: string, rules: Rule[]) => {
4142
const result = convertRulesToCreateACLRequests(rules, principal, host);
42-
await createAcls(result);
43-
44-
toast({
45-
status: 'success',
46-
description: 'ACLs created successfully',
47-
});
43+
const applyResult = await createAcls(result);
44+
handleResponses(toast, applyResult.errors, applyResult.created);
4845

4946
navigate(`/security/acls/${parsePrincipal(principal).name}/details`);
5047
};

frontend/src/components/pages/acls/new-acl/AclUpdatePage.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
parsePrincipal,
2121
type Rule,
2222
type SharedConfig,
23+
handleResponses,
2324
} from 'components/pages/acls/new-acl/ACL.model';
2425
import CreateACL from 'components/pages/acls/new-acl/CreateACL';
2526
import { useEffect } from 'react';
@@ -49,13 +50,8 @@ const AclUpdatePage = () => {
4950

5051
const updateAclMutation =
5152
(actualRules: Rule[], sharedConfig: SharedConfig) => async (_: string, _2: string, rules: Rule[]) => {
52-
await applyUpdates(actualRules, sharedConfig, rules);
53-
54-
// TODO: handle partial failures
55-
toast({
56-
status: 'success',
57-
description: 'ACLs updated successfully',
58-
});
53+
const applyResult = await applyUpdates(actualRules, sharedConfig, rules);
54+
handleResponses(toast, applyResult.errors, applyResult.created);
5955

6056
navigate(`/security/acls/${parsePrincipal(sharedConfig.principal).name}/details`);
6157
};

frontend/src/components/pages/roles/RoleCreatePage.tsx

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
PrincipalTypeRedpandaRole,
1717
parsePrincipal,
1818
type Rule,
19+
handleResponses,
1920
} from 'components/pages/acls/new-acl/ACL.model';
2021
import CreateACL from 'components/pages/acls/new-acl/CreateACL';
2122
import { CreateRoleRequestSchema } from 'protogen/redpanda/api/dataplane/v1/security_pb';
@@ -70,12 +71,8 @@ const RoleCreatePage = () => {
7071

7172
// Then create the ACLs for the role
7273
const result = convertRulesToCreateACLRequests(rules, principal, host);
73-
await createAcls(result);
74-
75-
toast({
76-
status: 'success',
77-
description: 'Role ACLs created successfully',
78-
});
74+
const applyResult = await createAcls(result);
75+
handleResponses(toast, applyResult.errors, applyResult.created);
7976

8077
navigate(`/security/roles/${roleName}/details`);
8178
} catch (error) {

frontend/src/components/pages/roles/RoleUpdatePage.tsx

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
import { useToast } from '@redpanda-data/ui';
1313
import {
1414
getOperationsForResourceType,
15+
handleResponses,
1516
ModeAllowAll,
1617
ModeDenyAll,
1718
OperationTypeAllow,
@@ -48,13 +49,8 @@ const RoleUpdatePage = () => {
4849

4950
const updateRoleAclMutation =
5051
(actualRules: Rule[], sharedConfig: SharedConfig) => async (_: string, _2: string, rules: Rule[]) => {
51-
await applyUpdates(actualRules, sharedConfig, rules);
52-
53-
// TODO: handle partial failures
54-
toast({
55-
status: 'success',
56-
description: 'Role ACLs updated successfully',
57-
});
52+
const applyResult = await applyUpdates(actualRules, sharedConfig, rules);
53+
handleResponses(toast, applyResult.errors, applyResult.created);
5854

5955
navigate(`/security/roles/${roleName}/details`);
6056
};

frontend/src/react-query/api/acl.tsx

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -497,8 +497,15 @@ export const useUpdateAclMutation = () => {
497497
),
498498
);
499499

500-
await Promise.all([...createResults, ...deleteResults]);
500+
const allResults = await Promise.allSettled([...createResults, ...deleteResults]);
501+
const errs = new Map<number, ConnectError>();
502+
const rejected = allResults.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
503+
rejected.forEach((result) => {
504+
const r = result.reason as ConnectError;
505+
errs.set(r.code, r);
506+
});
501507
await invalid();
508+
return { errors: errs.values().toArray(), created: rejected.length < allResults.length };
502509
};
503510

504511
return { applyUpdates };
@@ -526,8 +533,15 @@ export const useCreateAcls = () => {
526533
const { invalid } = useInvalidateAclsList();
527534

528535
const createAcls = async (acls: CreateACLRequest[]) => {
529-
await Promise.all(acls.map((r) => createACLMutation(r)));
536+
const results = await Promise.allSettled(acls.map((r) => createACLMutation(r)));
537+
const errs = new Map<number, ConnectError>();
538+
const rejected = results.filter((result): result is PromiseRejectedResult => result.status === 'rejected');
539+
rejected.forEach((result) => {
540+
const r = result.reason as ConnectError;
541+
errs.set(r.code, r);
542+
});
530543
await invalid();
544+
return { errors: errs.values().toArray(), created: rejected.length < results.length };
531545
};
532546
return { createAcls };
533547
};

0 commit comments

Comments
 (0)