- );
-});
-
-export function generatePassword(length: number, allowSpecialChars: boolean): string {
- if (length <= 0) return '';
-
- const lowercase = 'abcdefghijklmnopqrstuvwxyz'
- const uppercase = lowercase.toUpperCase();
- const numbers = '0123456789';
- const special = '.,&_+|[]/-()';
-
- let alphabet = lowercase + uppercase + numbers;
- if (allowSpecialChars) {
- alphabet += special;
- }
-
- const randomValues = new Uint32Array(length);
- crypto.getRandomValues(randomValues);
-
- let result = '';
- for (const n of randomValues) {
- const index = n % alphabet.length;
- const sym = alphabet[index];
-
- result += sym;
- }
-
- return result;
-}
diff --git a/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx b/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx
deleted file mode 100644
index 43f7c173d..000000000
--- a/frontend/src/components/pages/acls/CreateServiceAccountModal.tsx
+++ /dev/null
@@ -1,306 +0,0 @@
-import {
- Box,
- Button,
- Checkbox,
- CopyButton,
- Flex,
- FormField,
- Input,
- Modal,
- ModalBody,
- ModalCloseButton,
- ModalContent,
- ModalFooter,
- ModalHeader,
- ModalOverlay,
- PasswordInput,
- Tooltip,
- Text,
- createStandaloneToast,
- redpandaTheme,
- redpandaToastOptions,
- Grid,
- Alert,
- AlertIcon,
-} from '@redpanda-data/ui';
-import { generatePassword } from './CreateServiceAccountEditor';
-import { AclRequestDefault, CreateUserRequest } from '../../../state/restInterfaces';
-import { openModal } from '../../../utils/ModalContainer';
-import { observable } from 'mobx';
-import { observer } from 'mobx-react';
-import { ReloadOutlined } from '@ant-design/icons';
-import { SingleSelect } from '../../misc/Select';
-import { api } from '../../../state/backendApi';
-import { CheckCircleIcon } from '@chakra-ui/icons';
-
-const { ToastContainer, toast } = createStandaloneToast({
- theme: redpandaTheme,
- defaultOptions: {
- ...redpandaToastOptions.defaultOptions,
- isClosable: false,
- duration: 2000,
- },
-});
-
-export type CreateUserModalState = CreateUserRequest & {
- generateWithSpecialChars: boolean;
- step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION';
- isCreating: boolean;
- isValidUsername: boolean;
- isValidPassword: boolean;
-};
-
-const CreateUserRootModal = observer(
- (p: {
- state: CreateUserModalState;
- onCreateUser: (state: CreateUserModalState) => Promise;
- closeModal: () => void;
- }) => {
- return (
-
- {p.state.step === 'CREATE_USER' ? : }
-
- );
- }
-);
-
-const CreateUserModal = observer(
- (p: {
- state: CreateUserModalState;
- onCreateUser: (state: CreateUserModalState) => Promise;
- closeModal: () => void;
- }) => {
- const isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(p.state.username);
- const isValidPassword = p.state.password && p.state.password.length >= 4 && p.state.password.length <= 64;
-
- return (
- <>
-
-
-
- Create user
-
-
-
-
- (p.state.username = v.target.value)}
- width="100%"
- autoFocus
- spellCheck={false}
- placeholder="Username"
- autoComplete="off"
- />
-
-
-
-
-
- (p.state.password = e.target.value)}
- isInvalid={!isValidPassword}
- />
-
-
-
- (p.state.password = generatePassword(
- 30,
- p.state.generateWithSpecialChars
- ))
- }
- variant="ghost"
- width="35px"
- display="inline-flex"
- >
-
-
-
-
-
-
-
- {
- p.state.generateWithSpecialChars = e.target.checked;
- p.state.password = generatePassword(30, e.target.checked);
- }}
- >
- Generate with special characters
-
-
-
-
-
-
- options={[
- {
- value: 'SCRAM-SHA-256',
- label: 'SCRAM-SHA-256',
- },
- {
- value: 'SCRAM-SHA-512',
- label: 'SCRAM-SHA-512',
- },
- ]}
- value={p.state.mechanism}
- onChange={(e) => {
- p.state.mechanism = e;
- }}
- />
-
-
-
-
-
- {
- p.onCreateUser(p.state);
- }}
- isDisabled={p.state.isCreating || !isValidUsername || !isValidPassword}
- isLoading={p.state.isCreating}
- loadingText="Creating..."
- >
- Create
-
-
- Cancel
-
-
-
- >
- );
- }
-);
-
-const CreateUserConfirmationModal = observer((p: { state: CreateUserModalState; closeModal: () => void }) => {
- return (
- <>
-
-
-
-
-
-
- User created
-
-
-
-
-
-
- Username
-
-
-
-
- {p.state.username}
-
-
-
-
-
-
-
-
-
- Password
-
-
-
-
-
-
-
-
-
-
-
-
- Mechanism
-
-
-
- {p.state.mechanism}
-
-
-
-
-
-
- Password will be inaccessible after this modal is closed.
-
-
-
-
- Close
-
-
- >
- );
-});
-
-export function openCreateUserModal() {
- const state = observable({
- username: '',
- password: generatePassword(30, false),
- mechanism: 'SCRAM-SHA-256',
- generateWithSpecialChars: false,
- step: 'CREATE_USER',
- isCreating: false,
- isValidUsername: false,
- isValidPassword: false,
- } as CreateUserModalState);
-
- const onCreateUser = async (state: CreateUserModalState): Promise => {
- try {
- state.isCreating = true;
- await api.createServiceAccount({
- username: state.username,
- password: state.password,
- mechanism: state.mechanism,
- });
-
- // Refresh user list
- if (api.userData != null && !api.userData.canListAcls) return false;
- await Promise.allSettled([api.refreshAcls(AclRequestDefault, true), api.refreshServiceAccounts(true)]);
- state.step = 'CREATE_USER_CONFIRMATION';
- } catch (err) {
- toast({
- status: 'error',
- duration: null,
- isClosable: true,
- title: 'Failed to create user',
- description: String(err),
- });
- } finally {
- state.isCreating = false;
- }
- return true;
- };
-
- openModal(CreateUserRootModal, {
- state: state,
- onCreateUser: onCreateUser,
- });
-}
diff --git a/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx b/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx
new file mode 100644
index 000000000..c4ed3625e
--- /dev/null
+++ b/frontend/src/components/pages/acls/DeleteRoleConfirmModal.tsx
@@ -0,0 +1,28 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { FC } from 'react';
+import { Text, ConfirmItemDeleteModal } from '@redpanda-data/ui';
+
+
+export const DeleteRoleConfirmModal: FC<{
+ roleName: string;
+ numberOfPrincipals: number;
+ onConfirm: () => void;
+ buttonEl: React.ReactElement
+}> = ({ roleName, numberOfPrincipals, onConfirm, buttonEl }) => {
+ return (
+
+ This role is assigned to {numberOfPrincipals} {numberOfPrincipals === 1 ? 'principal' : 'principals'}. Deleting it will remove it from these principals and take those permissions away. The ACLs will all be deleted.
+ To restore the permissions, the role will need to be recreated and reassigned to these principals. To confirm, type the role name in the confirmation box below.
+
+ )
+}
diff --git a/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx b/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx
new file mode 100644
index 000000000..bdca71ed0
--- /dev/null
+++ b/frontend/src/components/pages/acls/DeleteUserConfirmModal.tsx
@@ -0,0 +1,26 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { FC } from 'react';
+import { Text, ConfirmItemDeleteModal } from '@redpanda-data/ui';
+
+
+export const DeleteUserConfirmModal: FC<{
+ userName: string;
+ onConfirm: () => void;
+ buttonEl: React.ReactElement
+}> = ({ userName, onConfirm, buttonEl }) => {
+ return (
+
+ This user has roles and ACLs assigned to it. Those roles and ACLs will not be deleted, but the user will need to be recreated and reassigned to them to be used again. To confirm, type the user name in the box below.
+
+ )
+}
diff --git a/frontend/src/components/pages/acls/Models.ts b/frontend/src/components/pages/acls/Models.ts
index b09f5608f..31b2f7c65 100644
--- a/frontend/src/components/pages/acls/Models.ts
+++ b/frontend/src/components/pages/acls/Models.ts
@@ -9,8 +9,11 @@
* by the Apache License, Version 2.0
*/
+import { comparer, observable } from 'mobx';
+import { api } from '../../../state/backendApi';
import { AclStrOperation, AclStrPermission, AclStrResourcePatternType, AclStrResourceType } from '../../../state/restInterfaces';
+export type PrincipalType = 'User' | 'RedpandaRole';
export type AclFlat = {
// AclResource
resourceType: AclStrResourceType;
@@ -25,8 +28,9 @@ export type AclFlat = {
}
export type AclPrincipalGroup = {
- principalType: string;
- principalName: string;
+ principalType: PrincipalType;
+ // This can only ever be a literal, or match anything (star in that case). No prefix or postfix matching
+ principalName: string | '*';
host: string;
@@ -146,7 +150,7 @@ export function createEmptyTransactionalIdAcl(): TransactionalIdACLs {
}
-export function collectTopicAcls(acls: AclFlat[]): TopicACLs[] {
+function collectTopicAcls(acls: AclFlat[]): TopicACLs[] {
const topics = acls
.filter(x => x.resourceType == 'Topic')
.groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`);
@@ -209,7 +213,7 @@ export function collectTopicAcls(acls: AclFlat[]): TopicACLs[] {
return topicAcls;
};
-export function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] {
+function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] {
const consumerGroups = acls
.filter(x => x.resourceType == 'Group')
.groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`);
@@ -262,7 +266,7 @@ export function collectConsumerGroupAcls(acls: AclFlat[]): ConsumerGroupACLs[] {
return consumerGroupAcls;
};
-export function collectClusterAcls(acls: AclFlat[]): ClusterACLs {
+function collectClusterAcls(acls: AclFlat[]): ClusterACLs {
const flatClusterAcls = acls.filter(x => x.resourceType == 'Cluster');
const clusterOperations = [
@@ -306,7 +310,7 @@ export function collectClusterAcls(acls: AclFlat[]): ClusterACLs {
return clusterAcls;
};
-export function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs[] {
+function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs[] {
const transactionalIds = acls
.filter(x => x.resourceType == 'TransactionalID')
.groupInto(x => `${x.resourcePatternType}: ${x.resourceName}`);
@@ -358,6 +362,106 @@ export function collectTransactionalIdAcls(acls: AclFlat[]): TransactionalIdACLs
};
+export const principalGroupsView = observable({
+
+ get flatAcls() {
+ const acls = api.ACLs;
+ if (!acls || !acls.aclResources || acls.aclResources.length == 0)
+ return [];
+
+ const flattened: AclFlat[] = [];
+ for (const res of acls.aclResources) {
+ for (const rule of res.acls) {
+
+ const flattenedEntry: AclFlat = {
+ resourceType: res.resourceType,
+ resourceName: res.resourceName,
+ resourcePatternType: res.resourcePatternType,
+
+ principal: rule.principal,
+ host: rule.host,
+ operation: rule.operation,
+ permissionType: rule.permissionType
+ };
+
+ flattened.push(flattenedEntry);
+ }
+ }
+
+ return observable(flattened);
+ },
+
+ get principalGroups(): AclPrincipalGroup[] {
+ const flat = this.flatAcls;
+
+ const g = flat.groupInto(f => {
+ const groupingKey = (f.principal ?? 'Any') + ' ' + (f.host ?? 'Any');
+ return groupingKey;
+ });
+
+ const result: AclPrincipalGroup[] = [];
+
+ for (const { items } of g) {
+ const { principal, host } = items[0];
+
+ let principalType: PrincipalType;
+ let principalName: string;
+ if (principal.includes(':')) {
+ const split = principal.split(':', 2);
+ principalType = split[0] as PrincipalType;
+ principalName = split[1];
+ } else {
+ principalType = 'User';
+ principalName = principal;
+ }
+
+ const principalGroup: AclPrincipalGroup = {
+ principalType,
+ principalName,
+ host,
+
+ topicAcls: collectTopicAcls(items),
+ consumerGroupAcls: collectConsumerGroupAcls(items),
+ clusterAcls: collectClusterAcls(items),
+ transactionalIdAcls: collectTransactionalIdAcls(items),
+
+ sourceEntries: items,
+ };
+ result.push(principalGroup);
+ }
+
+ // Add service accounts that exist but have no associated acl rules
+ const serviceAccounts = api.serviceAccounts?.users;
+ if (serviceAccounts) {
+ for (const acc of serviceAccounts) {
+ if (!result.any(g => g.principalName == acc)) {
+ // Doesn't have a group yet, create one
+ result.push({
+ principalType: 'User',
+ host: '',
+ principalName: acc,
+ topicAcls: [createEmptyTopicAcl()],
+ consumerGroupAcls: [createEmptyConsumerGroupAcl()],
+ transactionalIdAcls: [createEmptyTransactionalIdAcl()],
+ clusterAcls: createEmptyClusterAcl(),
+ sourceEntries: [],
+ });
+ }
+ }
+ }
+
+ return observable(result);
+ }
+}, undefined, {
+ equals: comparer.structural
+});
+
+
+/*
+ Sooner or later you want to go back from an 'AclPrincipalGroup' to flat ACLs.
+ Why? Because you'll need to call the remove/create acl apis and those only work with flat acls.
+ Use this method to convert your principal group back to a list of flat acls.
+*/
export function unpackPrincipalGroup(group: AclPrincipalGroup): AclFlat[] {
const flat: AclFlat[] = [];
diff --git a/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx b/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx
index 945542b80..739eeb6a4 100644
--- a/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx
+++ b/frontend/src/components/pages/acls/PrincipalGroupEditor.tsx
@@ -16,7 +16,7 @@ import { AclOperation, AclStrOperation, AclStrResourceType } from '../../../stat
import { AnimatePresence, animProps_radioOptionGroup, MotionDiv } from '../../../utils/animationProps';
import { Code, Label, LabelTooltip } from '../../../utils/tsxUtils';
import { HiOutlineTrash } from 'react-icons/hi';
-import { AclPrincipalGroup, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, ResourceACLs, unpackPrincipalGroup } from './Models';
+import { AclPrincipalGroup, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, PrincipalType, ResourceACLs, unpackPrincipalGroup } from './Models';
import { Operation } from './Operation';
import { Box, Button, Flex, Grid, HStack, Icon, Input, InputGroup, Modal, ModalBody, ModalContent, ModalFooter, ModalHeader, ModalOverlay, Text, useToast, VStack } from '@redpanda-data/ui';
import { SingleSelect } from '../../misc/Select';
@@ -105,6 +105,9 @@ export const AclPrincipalGroupEditor = observer((p: {
p.onClose();
}
+ if (!group.clusterAcls)
+ group.clusterAcls = createEmptyClusterAcl();
+
return (
{}}>
@@ -135,22 +138,17 @@ export const AclPrincipalGroupEditor = observer((p: {
}
>
-
-
+
+
+ isDisabled
value={group.principalType}
options={[
{
label: 'User',
value: 'User',
- },
- // {
- // label: 'Group',
- // value: 'Group'
- // }
+ }
]}
- onChange={(value) => {
- group.principalType = value
- }}
+ onChange={(value) => group.principalType = value}
/>
@@ -241,7 +239,7 @@ export const AclPrincipalGroupEditor = observer((p: {
Cluster
-
+
@@ -255,12 +253,16 @@ export const AclPrincipalGroupEditor = observer((p: {
)
});
-const ResourceACLsEditor = observer((p: {
+export const ResourceACLsEditor = observer((p: {
resource: ResourceACLs,
resourceType: AclStrResourceType,
onDelete?: () => void
}) => {
const res = p.resource;
+ if (!res) {
+ // Happens for clusterAcls?
+ return null;
+ }
const isCluster = !('selector' in res);
const isAllSet = res.all == 'Allow' || res.all == 'Deny';
@@ -272,59 +274,74 @@ const ResourceACLsEditor = observer((p: {
return (
- {isCluster ? (
- Applies to whole cluster
- ) : (
-
- )}
+
+ {isCluster ? (
+ Applies to whole cluster
+ ) : (
+
+ )}
-
+
{p.onDelete && (
-
-
-
+
+
+
+
-
+
)}
);
diff --git a/frontend/src/components/pages/acls/RoleCreate.tsx b/frontend/src/components/pages/acls/RoleCreate.tsx
new file mode 100644
index 000000000..235be5377
--- /dev/null
+++ b/frontend/src/components/pages/acls/RoleCreate.tsx
@@ -0,0 +1,59 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault } from '../../../state/restInterfaces';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import PageContent from '../../misc/PageContent';
+import { RoleForm } from './RoleForm';
+import { Text } from '@redpanda-data/ui';
+import { observer } from 'mobx-react';
+
+@observer
+class RoleCreatePage extends PageComponent {
+
+ initPage(p: PageInitHelper): void {
+ p.title = 'Create role';
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Roles', '/security/roles');
+ p.addBreadcrumb('Create role', '/security/roles/create');
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true),
+ rolesApi.refreshRoles()
+ ]);
+ }
+
+ render() {
+ if (api.ACLs?.aclResources === undefined) return DefaultSkeleton;
+ if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+
+ return (
+
+ A role is a named collection of ACLs which may have users (security principals) assigned to it. You can assign any number of roles to a given user.
+
+
+ );
+ }
+}
+
+export default RoleCreatePage;
+
diff --git a/frontend/src/components/pages/acls/RoleDetails.tsx b/frontend/src/components/pages/acls/RoleDetails.tsx
new file mode 100644
index 000000000..370e8944a
--- /dev/null
+++ b/frontend/src/components/pages/acls/RoleDetails.tsx
@@ -0,0 +1,160 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, RolePrincipal, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault } from '../../../state/restInterfaces';
+import { makeObservable, observable } from 'mobx';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import PageContent from '../../misc/PageContent';
+import { Button, DataTable, Flex, Heading, SearchField, Text } from '@redpanda-data/ui';
+import { principalGroupsView } from './Models';
+import { AclPrincipalGroupPermissionsTable } from './UserDetails';
+
+import { Link as ReactRouterLink } from 'react-router-dom';
+import { Box, Link as ChakraLink } from '@chakra-ui/react';
+import { DeleteRoleConfirmModal } from './DeleteRoleConfirmModal';
+
+
+@observer
+class RoleDetailsPage extends PageComponent<{ roleName: string }> {
+
+ @observable isDeleting: boolean = false;
+ @observable principalSearch: string = '';
+
+ constructor(p: any) {
+ super(p);
+ makeObservable(this);
+ this.deleteRole = this.deleteRole.bind(this);
+ }
+
+ initPage(p: PageInitHelper): void {
+ const roleName = decodeURIComponent(this.props.roleName);
+
+ p.title = 'Role details';
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Roles', '/security/roles');
+ p.addBreadcrumb(roleName, `/security/roles/${roleName}`);
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true),
+ ]);
+
+ await rolesApi.refreshRoles();
+ await rolesApi.refreshRoleMembers();
+ }
+
+ async deleteRole() {
+ this.isDeleting = true
+ try {
+ await rolesApi.deleteRole(this.props.roleName, true)
+ await rolesApi.refreshRoles();
+ await rolesApi.refreshRoleMembers(); // need to refresh assignments as well, otherwise users will still be marked as having that role, even though it doesn't exist anymore
+ } finally {
+ this.isDeleting = false;
+ }
+ appGlobal.history.push('/security/roles/');
+ }
+
+ render() {
+ if (api.ACLs?.aclResources === undefined) return DefaultSkeleton;
+ if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+
+ const aclPrincipalGroup = principalGroupsView.principalGroups.find(({
+ principalType,
+ principalName
+ }) => principalType === 'RedpandaRole' && principalName === this.props.roleName)
+
+ let members = rolesApi.roleMembers.get(this.props.roleName) ?? []
+ try {
+ const quickSearchRegExp = new RegExp(this.principalSearch, 'i')
+ members = members.filter(({name}) => name.match(quickSearchRegExp))
+ } catch (e) {
+ console.warn('Invalid expression')
+ }
+
+ const numberOfPrincipals = rolesApi.roleMembers.get(this.props.roleName)?.length ?? 0;
+
+ return <>
+
+
+ {
+ appGlobal.history.push(`/security/roles/${this.props.roleName}/edit`)
+ }}>
+ Edit
+
+
+ Delete
+
+ }
+ roleName={this.props.roleName}
+ />
+
+
+ Permissions
+ {aclPrincipalGroup ? : 'This role has no permissions assigned.'}
+
+
+
+ Principals
+ This role is assigned to {numberOfPrincipals} {numberOfPrincipals === 1 ? 'member' : 'members'}
+
+ (this.principalSearch = x)}
+ placeholderText="Filter by name"
+ />
+
+
+ data={members ?? []}
+ pagination
+ sorting
+ emptyText="No users found"
+ columns={[
+ {
+ id: 'name',
+ size: Infinity,
+ header: 'User',
+ cell: (ctx) => {
+ const entry = ctx.row.original;
+ return <>
+
+ {entry.name}
+
+ >
+ }
+ }
+ ]}
+ />
+
+
+ >
+ }
+
+}
+
+export default RoleDetailsPage;
+
diff --git a/frontend/src/components/pages/acls/RoleEditPage.tsx b/frontend/src/components/pages/acls/RoleEditPage.tsx
new file mode 100644
index 000000000..d09f97e1c
--- /dev/null
+++ b/frontend/src/components/pages/acls/RoleEditPage.tsx
@@ -0,0 +1,90 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault } from '../../../state/restInterfaces';
+import { makeObservable, observable } from 'mobx';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import PageContent from '../../misc/PageContent';
+import { RoleForm } from './RoleForm';
+import { principalGroupsView } from './Models';
+
+
+@observer
+class RoleEditPage extends PageComponent<{ roleName: string }> {
+
+ @observable allDataLoaded = false;
+
+ constructor(p: any) {
+ super(p);
+ makeObservable(this);
+ }
+
+ initPage(p: PageInitHelper): void {
+ const roleName = decodeURIComponent(this.props.roleName);
+
+ p.title = 'Edit role';
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Roles', '/security/roles');
+ p.addBreadcrumb(roleName, `/security/roles/${roleName}`);
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true),
+ ]);
+
+ await rolesApi.refreshRoles();
+ await rolesApi.refreshRoleMembers();
+
+ this.allDataLoaded = true
+ }
+
+ render() {
+ // if (api.ACLs?.aclResources === undefined) return DefaultSkeleton;
+ // if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+ if (!this.allDataLoaded) return DefaultSkeleton;
+
+ const aclPrincipalGroup = principalGroupsView.principalGroups.find(({
+ principalType,
+ principalName
+ }) => principalType === 'RedpandaRole' && principalName === this.props.roleName);
+
+ const principals = rolesApi.roleMembers.get(this.props.roleName)
+
+ return <>
+
+
+
+ >
+ }
+
+}
+
+export default RoleEditPage;
+
diff --git a/frontend/src/components/pages/acls/RoleForm.tsx b/frontend/src/components/pages/acls/RoleForm.tsx
new file mode 100644
index 000000000..25f5b7e14
--- /dev/null
+++ b/frontend/src/components/pages/acls/RoleForm.tsx
@@ -0,0 +1,287 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { Box, Button, Flex, FormField, Heading, HStack, Input, isSingleValue, Select, Tag, TagCloseButton, TagLabel } from '@redpanda-data/ui';
+import React, { useEffect, useMemo, useState } from 'react';
+import { AclPrincipalGroup, ClusterACLs, ConsumerGroupACLs, createEmptyClusterAcl, createEmptyConsumerGroupAcl, createEmptyTopicAcl, createEmptyTransactionalIdAcl, TopicACLs, TransactionalIdACLs, unpackPrincipalGroup } from './Models';
+import { observer, useLocalObservable } from 'mobx-react';
+import { ResourceACLsEditor } from './PrincipalGroupEditor';
+import { api, RolePrincipal, rolesApi } from '../../../state/backendApi';
+import { AclStrOperation, AclStrResourceType } from '../../../state/restInterfaces';
+import { useHistory } from 'react-router-dom'
+import { appGlobal } from '../../../state/appGlobal';
+
+type CreateRoleFormState = {
+ roleName: string;
+ allowAllOperations: boolean;
+ host: string;
+ topicACLs: TopicACLs[];
+ consumerGroupsACLs: ConsumerGroupACLs[];
+ transactionalIDACLs: TransactionalIdACLs[];
+ clusterACLs: ClusterACLs,
+ principals: RolePrincipal[]
+}
+
+type RoleFormProps = {
+ initialData?: Partial;
+}
+
+export const RoleForm = observer(({initialData}: RoleFormProps) => {
+ const history = useHistory()
+ const formState = useLocalObservable(() => ({
+ roleName: '',
+ allowAllOperations: false,
+ host: '',
+ topicACLs: [createEmptyTopicAcl()],
+ consumerGroupsACLs: [createEmptyConsumerGroupAcl()],
+ transactionalIDACLs: [createEmptyTransactionalIdAcl()],
+ clusterACLs: createEmptyClusterAcl(),
+ principals: [],
+ ...initialData,
+ }))
+
+ if (!formState.clusterACLs)
+ formState.clusterACLs = createEmptyClusterAcl();
+
+ const originalUsernames = useMemo(() => initialData?.principals?.map(({name}) => name) ?? [], [
+ initialData?.principals
+ ])
+ const currentUsernames = formState.principals.map(({ name }) => name) ?? []
+ const editMode: boolean = Boolean(initialData?.roleName)
+
+ const roleNameAlreadyExist = rolesApi.roles.includes(formState.roleName) && !editMode;
+
+ return (
+
+
+
+ );
+})
+
+const PrincipalSelector = observer((p: {state: RolePrincipal[]}) => {
+ const [searchValue, setSearchValue] = useState('');
+
+ useEffect(() => {
+ void api.refreshServiceAccounts();
+ }, []);
+
+ const state = p.state
+
+
+ return
+
+
+
+
+ {state.map((principal, idx) =>
+
+ {principal.name}
+ state.remove(principal)} />
+
+ )}
+
+
+})
diff --git a/frontend/src/components/pages/acls/UserCreate.tsx b/frontend/src/components/pages/acls/UserCreate.tsx
new file mode 100644
index 000000000..2ca81bc27
--- /dev/null
+++ b/frontend/src/components/pages/acls/UserCreate.tsx
@@ -0,0 +1,448 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault, CreateUserRequest } from '../../../state/restInterfaces';
+import { makeObservable, observable } from 'mobx';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import Section from '../../misc/Section';
+import PageContent from '../../misc/PageContent';
+import { Alert, AlertIcon, Box, Button, Checkbox, CopyButton, createStandaloneToast, Flex, FormField, Grid, Heading, Input, isSingleValue, PasswordInput, redpandaTheme, redpandaToastOptions, Result, Select, Tag, TagCloseButton, TagLabel, Text, Tooltip } from '@redpanda-data/ui';
+import { useEffect, useMemo, useState } from 'react';
+import { ReloadOutlined } from '@ant-design/icons';
+import { SingleSelect } from '../../misc/Select';
+import { Features } from '../../../state/supportedFeatures';
+
+const { ToastContainer, toast } = createStandaloneToast({
+ theme: redpandaTheme,
+ defaultOptions: {
+ ...redpandaToastOptions.defaultOptions,
+ isClosable: false,
+ duration: 2000,
+ },
+});
+
+export type CreateUserModalState = CreateUserRequest & {
+ generateWithSpecialChars: boolean;
+ step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION';
+ isCreating: boolean;
+ isValidUsername: boolean;
+ isValidPassword: boolean;
+ selectedRoles: string[];
+};
+
+@observer
+class UserCreatePage extends PageComponent<{}> {
+
+ @observable username: string = '';
+ @observable password: string = generatePassword(30, false);
+ @observable mechanism: 'SCRAM-SHA-256' | 'SCRAM-SHA-512' = 'SCRAM-SHA-256';
+
+ @observable isValidUsername: boolean = false;
+ @observable isValidPassword: boolean = false;
+
+ @observable generateWithSpecialChars: boolean = false;
+ @observable step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION' = 'CREATE_USER';
+ @observable isCreating: boolean = false;
+
+ @observable selectedRoles: string[] = [];
+
+ constructor(p: any) {
+ super(p);
+ makeObservable(this);
+ this.onCreateUser = this.onCreateUser.bind(this);
+ }
+
+ initPage(p: PageInitHelper): void {
+ p.title = 'Create user';
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Create user', '/security/users/create');
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true)
+ ]);
+ }
+
+ render() {
+ if (api.userData != null && !api.userData.canListAcls) return PermissionDenied;
+ if (api.ACLs?.aclResources === undefined) return DefaultSkeleton;
+ if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+
+ this.isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(this.username);
+ this.isValidPassword = Boolean(this.password) && this.password.length >= 4 && this.password.length <= 64;
+
+ const onCancel = () => appGlobal.history.push('/security/users');
+
+ return <>
+
+
+
+
+ {this.step === 'CREATE_USER'
+ ?
+ :
+ }
+
+
+ >
+ }
+
+ async onCreateUser(): Promise {
+ try {
+ this.isCreating = true;
+ await api.createServiceAccount({
+ username: this.username,
+ password: this.password,
+ mechanism: this.mechanism,
+ });
+
+ // Refresh user list
+ if (api.userData != null && !api.userData.canListAcls) return false;
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, true),
+ api.refreshServiceAccounts(true)
+ ]);
+
+ // Add the user to the selected roles
+ const roleAddPromises = [];
+ for (const r of this.selectedRoles) {
+ roleAddPromises.push(rolesApi.updateRoleMembership(r, [this.username], [], false));
+ }
+ await Promise.allSettled(roleAddPromises);
+
+ this.step = 'CREATE_USER_CONFIRMATION';
+ } catch (err) {
+ toast({
+ status: 'error',
+ duration: null,
+ isClosable: true,
+ title: 'Failed to create user',
+ description: String(err),
+ });
+ } finally {
+ this.isCreating = false;
+ }
+ return true;
+ };
+}
+
+export default UserCreatePage;
+
+
+const CreateUserModal = observer((p: {
+ state: CreateUserModalState;
+ onCreateUser: () => Promise;
+ onCancel: () => void;
+}) => {
+ const state = p.state;
+
+ const isValidUsername = /^[a-zA-Z0-9._@-]+$/.test(state.username);
+ const users = api.serviceAccounts?.users ?? []
+ const userAlreadyExists = users.includes(state.username)
+ const isValidPassword = state.password && state.password.length >= 4 && state.password.length <= 64;
+
+ const errorText = useMemo(() => {
+ if(!isValidUsername) {
+ return 'The username contains invalid characters. Use only letters, numbers, dots, underscores, at symbols, and hyphens.'
+ }
+
+ if(userAlreadyExists) {
+ return 'User already exist'
+ }
+ }, [isValidUsername, userAlreadyExists])
+
+ return (
+
+
+
+ (state.username = v.target.value)}
+ width="100%"
+ autoFocus
+ spellCheck={false}
+ placeholder="Username"
+ autoComplete="off"
+ />
+
+
+
+
+
+ (state.password = e.target.value)}
+ isInvalid={!isValidPassword}
+ />
+
+
+
+ (state.password = generatePassword(
+ 30,
+ state.generateWithSpecialChars
+ ))
+ }
+ variant="ghost"
+ width="35px"
+ display="inline-flex"
+ >
+
+
+
+
+
+
+
+ {
+ state.generateWithSpecialChars = e.target.checked;
+ state.password = generatePassword(30, e.target.checked);
+ }}
+ >
+ Generate with special characters
+
+
+
+
+
+
+ options={[
+ {
+ value: 'SCRAM-SHA-256',
+ label: 'SCRAM-SHA-256',
+ },
+ {
+ value: 'SCRAM-SHA-512',
+ label: 'SCRAM-SHA-512',
+ },
+ ]}
+ value={state.mechanism}
+ onChange={(e) => {
+ state.mechanism = e;
+ }}
+ />
+
+
+ {Features.rolesApi && <>
+
+
+
+ >
+ }
+
+
+
+
+
+ Create
+
+
+ Cancel
+
+
+
+ );
+}
+);
+
+const CreateUserConfirmationModal = observer((p: { state: CreateUserModalState; closeModal: () => void }) => {
+ return (
+ <>
+
+
+ {/* */}
+ User created successfully
+
+
+
+
+
+ You will not be able to view this password again. Make sure that it is copied and saved.
+
+
+
+
+ Username
+
+
+
+
+ {p.state.username}
+
+
+
+
+
+
+
+
+
+ Password
+
+
+
+
+
+
+
+
+
+
+
+
+ Mechanism
+
+
+
+ {p.state.mechanism}
+
+
+
+
+
+ Done
+
+
+ >
+ );
+});
+
+
+export const RoleSelector = observer((p: { state: string[] }) => {
+
+ // Make sure we have up to date role info
+ useEffect(() => {
+ rolesApi.refreshRoles();
+ rolesApi.refreshRoleMembers();
+ }, []);
+ const [searchValue, setSearchValue] = useState('');
+
+ const state = p.state;
+
+ const availableRoles = (rolesApi.roles ?? [])
+ .filter(r => !state.includes(r))
+ .map(r => ({ value: r }));
+
+ return
+
+
+
+
+ {state.map(role =>
+
+ {role}
+ state.remove(role)} />
+
+ )}
+
+
+});
+
+
+const PermissionDenied = <>
+
+
+
+ You are not allowed to view this page.
+
+ Contact the administrator if you think this is an error.
+
+ }
+ extra={
+ Redpanda Console documentation
+ }
+ />
+
+
+>
+
+
+export function generatePassword(length: number, allowSpecialChars: boolean): string {
+ if (length <= 0) return '';
+
+ const lowercase = 'abcdefghijklmnopqrstuvwxyz'
+ const uppercase = lowercase.toUpperCase();
+ const numbers = '0123456789';
+ const special = '.,&_+|[]/-()';
+
+ let alphabet = lowercase + uppercase + numbers;
+ if (allowSpecialChars) {
+ alphabet += special;
+ }
+
+ const randomValues = new Uint32Array(length);
+ crypto.getRandomValues(randomValues);
+
+ let result = '';
+ for (const n of randomValues) {
+ const index = n % alphabet.length;
+ const sym = alphabet[index];
+
+ result += sym;
+ }
+
+ return result;
+}
diff --git a/frontend/src/components/pages/acls/UserDetails.tsx b/frontend/src/components/pages/acls/UserDetails.tsx
new file mode 100644
index 000000000..b72140267
--- /dev/null
+++ b/frontend/src/components/pages/acls/UserDetails.tsx
@@ -0,0 +1,342 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault } from '../../../state/restInterfaces';
+import { makeObservable, observable } from 'mobx';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import PageContent from '../../misc/PageContent';
+import { Box, Button, DataTable, Flex, Heading, Text } from '@redpanda-data/ui';
+
+import { Link as ReactRouterLink } from 'react-router-dom';
+import { Link as ChakraLink } from '@chakra-ui/react';
+import { AclPrincipalGroup, principalGroupsView } from './Models';
+import { DeleteUserConfirmModal } from './DeleteUserConfirmModal';
+import { UserPermissionAssignments } from './UserPermissionAssignments';
+import { Features } from '../../../state/supportedFeatures';
+
+@observer
+class UserDetailsPage extends PageComponent<{ userName: string; }> {
+
+ @observable username: string = '';
+ @observable mechanism: 'SCRAM-SHA-256' | 'SCRAM-SHA-512' = 'SCRAM-SHA-256';
+
+ @observable isValidUsername: boolean = false;
+ @observable isValidPassword: boolean = false;
+
+ @observable generateWithSpecialChars: boolean = false;
+ @observable step: 'CREATE_USER' | 'CREATE_USER_CONFIRMATION' = 'CREATE_USER';
+ @observable isCreating: boolean = false;
+
+ @observable selectedRoles: string[] = [];
+
+ constructor(p: any) {
+ super(p);
+ makeObservable(this);
+ }
+
+ initPage(p: PageInitHelper): void {
+ p.title = 'Create user';
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Users', '/security/users');
+ p.addBreadcrumb(this.props.userName, '/security/users/');
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true),
+ rolesApi.refreshRoles()
+ ]);
+
+ await rolesApi.refreshRoleMembers();
+ }
+
+ render() {
+ if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+ const userName = this.props.userName;
+
+ const isServiceAccount = api.serviceAccounts.users.includes(userName);
+
+ return <>
+
+
+ appGlobal.history.push(`/security/users/${userName}/edit`)}>
+ Edit
+
+ {/* todo: refactor delete user dialog into a "fire and forget" dialog and use it in the overview list (and here) */}
+ {isServiceAccount &&
+ {
+ await api.deleteServiceAccount(userName);
+
+ // Remove user from all its roles
+ const promises = [];
+ for (const [roleName, members] of rolesApi.roleMembers) {
+ if (members.any(m => m.name == userName)) { // is this user part of this role?
+ // then remove it
+ promises.push(rolesApi.updateRoleMembership(roleName, [], [userName]));
+ }
+ }
+ await Promise.allSettled(promises);
+ await api.refreshServiceAccounts(true);
+ await rolesApi.refreshRoleMembers();
+ appGlobal.history.push('/security/users/');
+ }}
+ buttonEl={
+
+ Delete
+
+ }
+ userName={userName}
+ />
+ }
+
+
+ Permissions
+ Below are all of the permissions assigned to this SCRAM user.
+
+ {Features.rolesApi && <>
+ Assignments
+
+
+ >
+ }
+
+
+ >
+ }
+
+
+}
+
+export default UserDetailsPage;
+
+
+const PermissionAssignemntsDetails = observer((p: {
+ userName: string;
+}) => {
+ // Get all roles and ACLs matching this user
+ // For each "AclPrincipalGroup" show its name, then a table that shows the details
+ const roles: string[] = [];
+ for (const [roleName, members] of rolesApi.roleMembers) {
+ if (!members.any(m => m.name == p.userName))
+ continue; // this role doesn't contain our user
+ roles.push(roleName);
+ }
+
+ // Get all AclPrincipal groups, find the ones that apply
+ const groups = principalGroupsView.principalGroups.filter(g => {
+ if (g.principalType == 'User' && (g.principalName == p.userName || g.principalName === '*')) return true;
+ if (g.principalType == 'RedpandaRole' && roles.includes(g.principalName)) return true;
+ return false;
+ });
+
+ console.log('groups: ' + groups.map(g => g.principalName + ' (' + g.principalType + ')').join(', '));
+
+ return <>
+ {groups.map(r =>
+ <>
+ {
+ r.principalType == 'RedpandaRole'
+ ? {r.principalName}
+ : User: {r.principalName}
+ }
+
+ >
+ )}
+ >
+});
+
+export const AclPrincipalGroupPermissionsTable = observer((p: { group: AclPrincipalGroup }) => {
+
+ const entries: {
+ type: string;
+ selector: string;
+ operations: {
+ allow: string[];
+ deny: string[];
+ };
+ }[] = [];
+
+ // Convert all entries of the group into a table row
+ const group = p.group;
+ for (const topicAcl of group.topicAcls) {
+ const allow: string[] = [];
+ const deny: string[] = [];
+
+ if (topicAcl.all == 'Allow')
+ allow.push('All');
+ else if (topicAcl.all == 'Deny')
+ deny.push('All');
+ else {
+ for (const [permName, value] of Object.entries(topicAcl.permissions)) {
+ if (value == 'Allow')
+ allow.push(permName);
+ if (value == 'Deny')
+ deny.push(permName);
+ }
+ }
+
+ if (allow.length == 0 && deny.length == 0)
+ continue;
+
+ entries.push({
+ type: 'Topic',
+ selector: topicAcl.selector,
+ operations: { allow, deny }
+ })
+ }
+
+ for (const groupAcl of group.consumerGroupAcls) {
+ const allow: string[] = [];
+ const deny: string[] = [];
+
+ if (groupAcl.all == 'Allow')
+ allow.push('All');
+ else if (groupAcl.all == 'Deny')
+ deny.push('All');
+ else {
+ for (const [permName, value] of Object.entries(groupAcl.permissions)) {
+ if (value == 'Allow')
+ allow.push(permName);
+ if (value == 'Deny')
+ deny.push(permName);
+ }
+ }
+
+ if (allow.length == 0 && deny.length == 0)
+ continue;
+
+ entries.push({
+ type: 'ConsumerGroup',
+ selector: groupAcl.selector,
+ operations: { allow, deny }
+ })
+ }
+
+ for (const transactId of group.transactionalIdAcls) {
+ const allow: string[] = [];
+ const deny: string[] = [];
+
+ if (transactId.all == 'Allow')
+ allow.push('All');
+ else if (transactId.all == 'Deny')
+ deny.push('All');
+ else {
+ for (const [permName, value] of Object.entries(transactId.permissions)) {
+ if (value == 'Allow')
+ allow.push(permName);
+ if (value == 'Deny')
+ deny.push(permName);
+ }
+ }
+
+ if (allow.length == 0 && deny.length == 0)
+ continue;
+
+ entries.push({
+ type: 'TransactionalID',
+ selector: transactId.selector,
+ operations: { allow, deny }
+ })
+ }
+
+ // Cluster
+ {
+ const clusterAcls = group.clusterAcls;
+
+ const allow: string[] = [];
+ const deny: string[] = [];
+
+ if (clusterAcls.all == 'Allow')
+ allow.push('All');
+ else if (clusterAcls.all == 'Deny')
+ deny.push('All');
+ else {
+ for (const [permName, value] of Object.entries(clusterAcls.permissions)) {
+ if (value == 'Allow')
+ allow.push(permName);
+ if (value == 'Deny')
+ deny.push(permName);
+ }
+ }
+
+ // Cluster only appears once, so it won't be filtered automatically,
+ // we need to manually skip this entry if there isn't any content
+ if (allow.length + deny.length > 0)
+ entries.push({
+ type: 'Cluster',
+ selector: '',
+ operations: { allow, deny }
+ })
+ }
+
+ if (entries.length == 0)
+ return <>No permissions assigned>;
+
+ return <>
+ {
+
+ const allow = record.operations.allow;
+ const deny = record.operations.deny;
+
+ return
+
+ {allow.length > 0
+ ? <>
+ Allow:
+ {allow.join(', ')}
+ >
+ : {' '}
+ }
+
+
+ {deny.length > 0
+ ? <>
+ Deny:
+ {deny.join(', ')}
+ >
+ : {' '}
+ }
+
+
+ },
+ }
+ ]}
+ />
+
+ >
+})
diff --git a/frontend/src/components/pages/acls/UserEdit.tsx b/frontend/src/components/pages/acls/UserEdit.tsx
new file mode 100644
index 000000000..22cf4cae3
--- /dev/null
+++ b/frontend/src/components/pages/acls/UserEdit.tsx
@@ -0,0 +1,157 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { PageComponent, PageInitHelper } from '../Page';
+import { api, rolesApi } from '../../../state/backendApi';
+import { AclRequestDefault } from '../../../state/restInterfaces';
+import { makeObservable, observable } from 'mobx';
+import { appGlobal } from '../../../state/appGlobal';
+import { DefaultSkeleton } from '../../../utils/tsxUtils';
+import PageContent from '../../misc/PageContent';
+import { Box, Button, Flex, Heading, Input, createStandaloneToast, redpandaTheme, redpandaToastOptions } from '@redpanda-data/ui';
+import { RoleSelector } from './UserCreate';
+import { UpdateRoleMembershipResponse } from '../../../protogen/redpanda/api/console/v1alpha1/security_pb';
+import { Features } from '../../../state/supportedFeatures';
+
+const { ToastContainer, toast } = createStandaloneToast({
+ theme: redpandaTheme,
+ defaultOptions: {
+ ...redpandaToastOptions.defaultOptions,
+ isClosable: false,
+ duration: 2000,
+ },
+});
+
+@observer
+class UserEditPage extends PageComponent<{ userName: string; }> {
+
+ @observable originalRoles: Set | undefined = undefined;
+ @observable selectedRoles: string[] | undefined = undefined;
+ @observable isSaving = false;
+
+ constructor(p: any) {
+ super(p);
+ makeObservable(this);
+ }
+
+ initPage(p: PageInitHelper): void {
+ p.title = 'Edit user ' + this.props.userName;
+ p.addBreadcrumb('Access control', '/security');
+ p.addBreadcrumb('Users', '/security/users');
+ p.addBreadcrumb(this.props.userName, `/security/users/${this.props.userName}/edit`);
+
+ this.refreshData(true);
+ appGlobal.onRefresh = () => this.refreshData(true);
+ }
+
+ async refreshData(force: boolean) {
+ if (api.userData != null && !api.userData.canListAcls) return;
+
+ await Promise.allSettled([
+ api.refreshAcls(AclRequestDefault, force),
+ api.refreshServiceAccounts(true),
+ rolesApi.refreshRoles()
+ ]);
+
+ await rolesApi.refreshRoleMembers();
+ }
+
+ render() {
+ if (!api.serviceAccounts || !api.serviceAccounts.users) return DefaultSkeleton;
+
+ const userName = this.props.userName;
+
+ // Load roles the user is assigned to, for this we need all roles
+ if (this.selectedRoles == undefined || this.originalRoles == undefined) {
+ this.originalRoles = new Set();
+ for (const [name, members] of rolesApi.roleMembers) {
+ if (members.any(x => x.name == userName))
+ this.originalRoles.add(name);
+ }
+
+ this.selectedRoles = [...this.originalRoles.values()];
+ }
+
+ const onCancel = () => appGlobal.history.push(`/security/users/${userName}/details`);
+
+
+ // Check if there are any roles removed, added, or replaced
+ // only then will the save button be enabled
+ // First check if they have the same number of roles
+ const originalRoles = [...this.originalRoles.values()];
+
+ const addedRoles = this.selectedRoles.except(originalRoles);
+ const removedRoles = originalRoles.except(this.selectedRoles);
+
+ const hasChanges = addedRoles.length > 0 || removedRoles.length > 0
+
+ const onSave = async () => {
+ if (Features.rolesApi) {
+ const promises: Promise[] = [];
+
+ // Remove user from "removedRoles"
+ for (const r of removedRoles)
+ promises.push(rolesApi.updateRoleMembership(r, [], [userName], false));
+ // Add to newly selected roles
+ for (const r of addedRoles)
+ promises.push(rolesApi.updateRoleMembership(r, [userName], [], false));
+
+ await Promise.allSettled(promises);
+
+ // Update roles and memberships so that the change is reflected in the ui
+ await rolesApi.refreshRoles();
+ await rolesApi.refreshRoleMembers();
+ }
+
+ toast({
+ status: 'success',
+ title: `${addedRoles.length} roles added, ${removedRoles.length} removed from user ${userName}`
+ });
+ appGlobal.history.push(`/security/users/${userName}/details`);
+ };
+
+ return <>
+
+
+
+
+ Create user
+ This is a Redpanda SASL/SCRAM user. For other types of users and authentication, see our documentation.
+
+ Username
+
+
+ {Features.rolesApi && <>
+ Assignments
+
+ >
+ }
+
+
+
+ Save
+
+
+ Cancel
+
+
+
+
+ >
+ }
+
+
+}
+
+export default UserEditPage;
+
+
diff --git a/frontend/src/components/pages/acls/UserPermissionAssignments.tsx b/frontend/src/components/pages/acls/UserPermissionAssignments.tsx
new file mode 100644
index 000000000..2ceee91d6
--- /dev/null
+++ b/frontend/src/components/pages/acls/UserPermissionAssignments.tsx
@@ -0,0 +1,56 @@
+/**
+ * Copyright 2022 Redpanda Data, Inc.
+ *
+ * Use of this software is governed by the Business Source License
+ * included in the file https://github.com/redpanda-data/redpanda/blob/dev/licenses/bsl.md
+ *
+ * As of the Change Date specified in that file, in accordance with
+ * the Business Source License, use of this software will be governed
+ * by the Apache License, Version 2.0
+ */
+
+import { observer } from 'mobx-react';
+import { rolesApi } from '../../../state/backendApi';
+import { Link as ReactRouterLink } from 'react-router-dom';
+import { Link as ChakraLink } from '@chakra-ui/react';
+import React from 'react';
+import { Box, Flex, Text } from '@redpanda-data/ui';
+
+export const UserPermissionAssignments = observer(({
+ userName,
+ showMaxItems = Infinity
+ }: {
+ userName: string;
+ showMaxItems?: number;
+}) => {
+ // Get all roles, and ACL sets that apply to this user
+ const roles = [];
+ for (const [roleName, members] of rolesApi.roleMembers) {
+ if (!members.any(m => m.name == userName)) {
+ continue; // this role doesn't contain our user
+ }
+ roles.push(roleName);
+ }
+
+ const elements: JSX.Element[] = [];
+
+ const numberOfVisibleElements = Math.min(roles.length, showMaxItems);
+ const numberOfHiddenElements = showMaxItems === Infinity ? 0 : Math.max(0, roles.length - showMaxItems);
+
+ for (let i = 0; i < numberOfVisibleElements; i++) {
+ const r = roles[i];
+ elements.push(
+
+ {r}
+
+ );
+
+ if (i < numberOfVisibleElements - 1)
+ elements.push({', '});
+ }
+
+ return
+ {elements}
+ {numberOfHiddenElements !== 0 && {`+${numberOfHiddenElements} more`}}
+ ;
+});
diff --git a/frontend/src/components/routes.tsx b/frontend/src/components/routes.tsx
index ae7d276f7..e6b1daebe 100644
--- a/frontend/src/components/routes.tsx
+++ b/frontend/src/components/routes.tsx
@@ -23,7 +23,7 @@ import AdminPage from './pages/admin/AdminPage';
import { api } from '../state/backendApi';
import SchemaList from './pages/schemas/Schema.List';
import SchemaDetailsView from './pages/schemas/Schema.Details';
-import AclList from './pages/acls/Acl.List';
+import AclList, { AclListTab } from './pages/acls/Acl.List';
import { HomeIcon, CogIcon, CollectionIcon, CubeTransparentIcon, FilterIcon, ShieldCheckIcon, LinkIcon, ScaleIcon, BeakerIcon } from '@heroicons/react/outline';
import ReassignPartitions from './pages/reassign-partitions/ReassignPartitions';
import { Feature, FeatureEntry, isSupported, shouldHideIfNotSupported } from '../state/supportedFeatures';
@@ -41,6 +41,12 @@ import { BrokerDetails } from './pages/overview/Broker.Details';
import EditSchemaCompatibilityPage from './pages/schemas/EditCompatibility';
import { SchemaCreatePage, SchemaAddVersionPage } from './pages/schemas/Schema.Create';
import { TopicProducePage } from './pages/topics/Topic.Produce';
+import UserCreatePage from './pages/acls/UserCreate';
+import UserDetailsPage from './pages/acls/UserDetails';
+import UserEditPage from './pages/acls/UserEdit';
+import RoleCreatePage from './pages/acls/RoleCreate';
+import RoleDetailsPage from './pages/acls/RoleDetails';
+import RoleEditPage from './pages/acls/RoleEditPage';
//
// Route Types
@@ -247,9 +253,19 @@ export const APP_ROUTES: IRouteEntry[] = [
),
MakeRoute<{ groupId: string }>('/groups/:groupId/', GroupDetails, 'Consumer Groups'),
- MakeRoute<{}>('/acls', AclList, 'Security', ShieldCheckIcon, true,
+ MakeRoute<{}>('/security', AclList, 'Security', ShieldCheckIcon, true,
routeVisibility(true, [], ['canListAcls'])
),
+ MakeRoute<{ tab: AclListTab }>('/security/:tab', AclList, 'Security'),
+
+ MakeRoute<{}>('/security/users/create', UserCreatePage, 'Security'),
+ MakeRoute<{ userName: string }>('/security/users/:userName/details', UserDetailsPage, 'Security'),
+ MakeRoute<{ userName: string }>('/security/users/:userName/edit', UserEditPage, 'Security'),
+
+ MakeRoute<{}>('/security/roles/create', RoleCreatePage, 'Security'),
+ MakeRoute<{ roleName: string }>('/security/roles/:roleName/details', RoleDetailsPage, 'Security'),
+ MakeRoute<{ roleName: string }>('/security/roles/:roleName/edit', RoleEditPage, 'Security'),
+
MakeRoute<{}>('/quotas', QuotasList, 'Quotas', ScaleIcon, true,
routeVisibility(true, [Feature.GetQuotas], ['canListQuotas'])
diff --git a/frontend/src/config.ts b/frontend/src/config.ts
index a5c41c387..638bffc0b 100644
--- a/frontend/src/config.ts
+++ b/frontend/src/config.ts
@@ -19,6 +19,7 @@ import { APP_ROUTES } from './components/routes';
import { Interceptor as ConnectRpcInterceptor, StreamRequest, UnaryRequest, createPromiseClient, PromiseClient } from '@connectrpc/connect';
import { createConnectTransport } from '@connectrpc/connect-web';
import { ConsoleService } from './protogen/redpanda/api/console/v1alpha1/console_service_connect';
+import { SecurityService } from './protogen/redpanda/api/console/v1alpha1/security_connect';
declare const __webpack_public_path__: string;
@@ -63,6 +64,7 @@ export interface Breadcrumb {
interface Config {
restBasePath: string;
consoleClient?: PromiseClient;
+ securityClient?: PromiseClient;
fetch: WindowOrWorkerGlobalScope['fetch'];
assetsPath: string;
jwt?: string;
@@ -94,14 +96,16 @@ const setConfig = ({ fetch, urlOverride, jwt, isServerless, ...args }: SetConfig
interceptors: [addBearerTokenInterceptor]
});
- const grpcClient = createPromiseClient(ConsoleService, transport);
+ const consoleGrpcClient = createPromiseClient(ConsoleService, transport);
+ const securityGrpcClient = createPromiseClient(SecurityService, transport);
Object.assign(config, {
jwt,
isServerless,
restBasePath: getRestBasePath(urlOverride?.rest),
fetch: fetch ?? window.fetch.bind(window),
assetsPath: assetsUrl ?? getBasePath(),
- consoleClient: grpcClient,
+ consoleClient: consoleGrpcClient,
+ securityClient: securityGrpcClient,
...args,
});
return config;
diff --git a/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts
new file mode 100644
index 000000000..63f6d96cf
--- /dev/null
+++ b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_connect.ts
@@ -0,0 +1,85 @@
+// @generated by protoc-gen-connect-es v1.2.0 with parameter "target=ts,import_extension="
+// @generated from file redpanda/api/console/v1alpha1/security.proto (package redpanda.api.console.v1alpha1, syntax proto3)
+/* eslint-disable */
+// @ts-nocheck
+
+import { CreateRoleRequest, CreateRoleResponse, DeleteRoleRequest, DeleteRoleResponse, GetRoleRequest, GetRoleResponse, ListRoleMembersRequest, ListRoleMembersResponse, ListRolesRequest, ListRolesResponse, UpdateRoleMembershipRequest, UpdateRoleMembershipResponse } from "./security_pb";
+import { MethodKind } from "@bufbuild/protobuf";
+
+/**
+ * @generated from service redpanda.api.console.v1alpha1.SecurityService
+ */
+export const SecurityService = {
+ typeName: "redpanda.api.console.v1alpha1.SecurityService",
+ methods: {
+ /**
+ * ListRoles lists all the roles based on optional filter.
+ *
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.ListRoles
+ */
+ listRoles: {
+ name: "ListRoles",
+ I: ListRolesRequest,
+ O: ListRolesResponse,
+ kind: MethodKind.Unary,
+ },
+ /**
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.CreateRole
+ */
+ createRole: {
+ name: "CreateRole",
+ I: CreateRoleRequest,
+ O: CreateRoleResponse,
+ kind: MethodKind.Unary,
+ },
+ /**
+ * GetRole retrieves the specific role.
+ *
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.GetRole
+ */
+ getRole: {
+ name: "GetRole",
+ I: GetRoleRequest,
+ O: GetRoleResponse,
+ kind: MethodKind.Unary,
+ },
+ /**
+ * DeleteRole deletes the role from the system.
+ *
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.DeleteRole
+ */
+ deleteRole: {
+ name: "DeleteRole",
+ I: DeleteRoleRequest,
+ O: DeleteRoleResponse,
+ kind: MethodKind.Unary,
+ },
+ /**
+ * ListRoleMembership lists all the members assigned to a role based on optional filter.
+ *
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.ListRoleMembers
+ */
+ listRoleMembers: {
+ name: "ListRoleMembers",
+ I: ListRoleMembersRequest,
+ O: ListRoleMembersResponse,
+ kind: MethodKind.Unary,
+ },
+ /**
+ * UpdateRoleMembership updates role membership.
+ * Partially update role membership, adding or removing from a role
+ * ONLY those members listed in the “add” or “remove” fields, respectively.
+ * Adding a member that is already assigned to the role (or removing one that is not) is a no-op,
+ * and the rest of the members will be added and removed and reported.
+ *
+ * @generated from rpc redpanda.api.console.v1alpha1.SecurityService.UpdateRoleMembership
+ */
+ updateRoleMembership: {
+ name: "UpdateRoleMembership",
+ I: UpdateRoleMembershipRequest,
+ O: UpdateRoleMembershipResponse,
+ kind: MethodKind.Unary,
+ },
+ }
+} as const;
+
diff --git a/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts
new file mode 100644
index 000000000..fa3043e85
--- /dev/null
+++ b/frontend/src/protogen/redpanda/api/console/v1alpha1/security_pb.ts
@@ -0,0 +1,795 @@
+// @generated by protoc-gen-es v1.6.0 with parameter "target=ts,import_extension="
+// @generated from file redpanda/api/console/v1alpha1/security.proto (package redpanda.api.console.v1alpha1, syntax proto3)
+/* eslint-disable */
+// @ts-nocheck
+
+import type { BinaryReadOptions, FieldList, JsonReadOptions, JsonValue, PartialMessage, PlainMessage } from "@bufbuild/protobuf";
+import { Message, proto3 } from "@bufbuild/protobuf";
+
+/**
+ * Role defines a role in the system.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.Role
+ */
+export class Role extends Message {
+ /**
+ * The name of the role.
+ *
+ * @generated from field: string name = 1;
+ */
+ name = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.Role";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): Role {
+ return new Role().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): Role {
+ return new Role().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): Role {
+ return new Role().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: Role | PlainMessage | undefined, b: Role | PlainMessage | undefined): boolean {
+ return proto3.util.equals(Role, a, b);
+ }
+}
+
+/**
+ * ListRolesRequest is the request for ListRoles.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRolesRequest
+ */
+export class ListRolesRequest extends Message {
+ /**
+ * Optional filter.
+ *
+ * @generated from field: optional redpanda.api.console.v1alpha1.ListRolesRequest.Filter filter = 1;
+ */
+ filter?: ListRolesRequest_Filter;
+
+ /**
+ * Page size.
+ *
+ * @generated from field: int32 page_size = 2;
+ */
+ pageSize = 0;
+
+ /**
+ * Value of the next_page_token field returned by the previous response.
+ * If not provided, the system assumes the first page is requested.
+ *
+ * @generated from field: string page_token = 3;
+ */
+ pageToken = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "filter", kind: "message", T: ListRolesRequest_Filter, opt: true },
+ { no: 2, name: "page_size", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
+ { no: 3, name: "page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesRequest {
+ return new ListRolesRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesRequest {
+ return new ListRolesRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRolesRequest {
+ return new ListRolesRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRolesRequest | PlainMessage | undefined, b: ListRolesRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRolesRequest, a, b);
+ }
+}
+
+/**
+ * Filter options.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRolesRequest.Filter
+ */
+export class ListRolesRequest_Filter extends Message {
+ /**
+ * Filter results only roles named with the prefix.
+ *
+ * @generated from field: string name_prefix = 1;
+ */
+ namePrefix = "";
+
+ /**
+ * Filter results to only roles with names which contain the string.
+ *
+ * @generated from field: string name_contains = 2;
+ */
+ nameContains = "";
+
+ /**
+ * Return only roles assigned to this principal.
+ *
+ * @generated from field: string principal = 3;
+ */
+ principal = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesRequest.Filter";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "name_prefix", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "name_contains", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 3, name: "principal", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesRequest_Filter {
+ return new ListRolesRequest_Filter().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesRequest_Filter {
+ return new ListRolesRequest_Filter().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRolesRequest_Filter {
+ return new ListRolesRequest_Filter().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRolesRequest_Filter | PlainMessage | undefined, b: ListRolesRequest_Filter | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRolesRequest_Filter, a, b);
+ }
+}
+
+/**
+ * ListRolesResponse is the response for ListRoles.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRolesResponse
+ */
+export class ListRolesResponse extends Message {
+ /**
+ * The roles in the system.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.Role roles = 1;
+ */
+ roles: Role[] = [];
+
+ /**
+ * Token to retrieve the next page.
+ *
+ * @generated from field: string next_page_token = 2;
+ */
+ nextPageToken = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRolesResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "roles", kind: "message", T: Role, repeated: true },
+ { no: 2, name: "next_page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRolesResponse {
+ return new ListRolesResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRolesResponse {
+ return new ListRolesResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRolesResponse {
+ return new ListRolesResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRolesResponse | PlainMessage | undefined, b: ListRolesResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRolesResponse, a, b);
+ }
+}
+
+/**
+ * CreateRoleRequest is the request for CreateRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.CreateRoleRequest
+ */
+export class CreateRoleRequest extends Message {
+ /**
+ * The role to create.
+ *
+ * @generated from field: redpanda.api.console.v1alpha1.Role role = 1;
+ */
+ role?: Role;
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.CreateRoleRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role", kind: "message", T: Role },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): CreateRoleRequest {
+ return new CreateRoleRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): CreateRoleRequest {
+ return new CreateRoleRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): CreateRoleRequest {
+ return new CreateRoleRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: CreateRoleRequest | PlainMessage | undefined, b: CreateRoleRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(CreateRoleRequest, a, b);
+ }
+}
+
+/**
+ * CreateRoleResponse is the response for CreateRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.CreateRoleResponse
+ */
+export class CreateRoleResponse extends Message {
+ /**
+ * The role.
+ *
+ * @generated from field: redpanda.api.console.v1alpha1.Role role = 1;
+ */
+ role?: Role;
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.CreateRoleResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role", kind: "message", T: Role },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): CreateRoleResponse {
+ return new CreateRoleResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): CreateRoleResponse {
+ return new CreateRoleResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): CreateRoleResponse {
+ return new CreateRoleResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: CreateRoleResponse | PlainMessage | undefined, b: CreateRoleResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(CreateRoleResponse, a, b);
+ }
+}
+
+/**
+ * CreateRoleRequest is the request for CreateRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.GetRoleRequest
+ */
+export class GetRoleRequest extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.GetRoleRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): GetRoleRequest {
+ return new GetRoleRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): GetRoleRequest {
+ return new GetRoleRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): GetRoleRequest {
+ return new GetRoleRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: GetRoleRequest | PlainMessage | undefined, b: GetRoleRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(GetRoleRequest, a, b);
+ }
+}
+
+/**
+ * GetRoleResponse is the response to GetRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.GetRoleResponse
+ */
+export class GetRoleResponse extends Message {
+ /**
+ * The Role.
+ *
+ * @generated from field: redpanda.api.console.v1alpha1.Role role = 1;
+ */
+ role?: Role;
+
+ /**
+ * Members assigned to the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership members = 2;
+ */
+ members: RoleMembership[] = [];
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.GetRoleResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role", kind: "message", T: Role },
+ { no: 2, name: "members", kind: "message", T: RoleMembership, repeated: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): GetRoleResponse {
+ return new GetRoleResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): GetRoleResponse {
+ return new GetRoleResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): GetRoleResponse {
+ return new GetRoleResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: GetRoleResponse | PlainMessage | undefined, b: GetRoleResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(GetRoleResponse, a, b);
+ }
+}
+
+/**
+ * DeleteRoleRequest is the request for DeleteRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.DeleteRoleRequest
+ */
+export class DeleteRoleRequest extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ /**
+ * Whether to delete the ACLs bound to the role.
+ *
+ * @generated from field: bool delete_acls = 2;
+ */
+ deleteAcls = false;
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.DeleteRoleRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "delete_acls", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): DeleteRoleRequest {
+ return new DeleteRoleRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): DeleteRoleRequest {
+ return new DeleteRoleRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): DeleteRoleRequest {
+ return new DeleteRoleRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: DeleteRoleRequest | PlainMessage | undefined, b: DeleteRoleRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(DeleteRoleRequest, a, b);
+ }
+}
+
+/**
+ * DeleteRoleResponse is the response for DeleteRole.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.DeleteRoleResponse
+ */
+export class DeleteRoleResponse extends Message {
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.DeleteRoleResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): DeleteRoleResponse {
+ return new DeleteRoleResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): DeleteRoleResponse {
+ return new DeleteRoleResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): DeleteRoleResponse {
+ return new DeleteRoleResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: DeleteRoleResponse | PlainMessage | undefined, b: DeleteRoleResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(DeleteRoleResponse, a, b);
+ }
+}
+
+/**
+ * List role members for a role. That is user principals assigned to that role.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersRequest
+ */
+export class ListRoleMembersRequest extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ /**
+ * Optional filter.
+ *
+ * @generated from field: optional redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter filter = 2;
+ */
+ filter?: ListRoleMembersRequest_Filter;
+
+ /**
+ * Page size.
+ *
+ * @generated from field: int32 page_size = 3;
+ */
+ pageSize = 0;
+
+ /**
+ * Value of the next_page_token field returned by the previous response.
+ * If not provided, the system assumes the first page is requested.
+ *
+ * @generated from field: string page_token = 4;
+ */
+ pageToken = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "filter", kind: "message", T: ListRoleMembersRequest_Filter, opt: true },
+ { no: 3, name: "page_size", kind: "scalar", T: 5 /* ScalarType.INT32 */ },
+ { no: 4, name: "page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersRequest {
+ return new ListRoleMembersRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersRequest {
+ return new ListRoleMembersRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersRequest {
+ return new ListRoleMembersRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRoleMembersRequest | PlainMessage | undefined, b: ListRoleMembersRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRoleMembersRequest, a, b);
+ }
+}
+
+/**
+ * Filter options.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter
+ */
+export class ListRoleMembersRequest_Filter extends Message {
+ /**
+ * Filter results to only members with names which contain the string.
+ *
+ * @generated from field: string name_contains = 1;
+ */
+ nameContains = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersRequest.Filter";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "name_contains", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersRequest_Filter {
+ return new ListRoleMembersRequest_Filter().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersRequest_Filter {
+ return new ListRoleMembersRequest_Filter().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersRequest_Filter {
+ return new ListRoleMembersRequest_Filter().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRoleMembersRequest_Filter | PlainMessage | undefined, b: ListRoleMembersRequest_Filter | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRoleMembersRequest_Filter, a, b);
+ }
+}
+
+/**
+ * ListRoleMembersResponse is the response for ListRoleMembers.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.ListRoleMembersResponse
+ */
+export class ListRoleMembersResponse extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ /**
+ * Members assigned to the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership members = 2;
+ */
+ members: RoleMembership[] = [];
+
+ /**
+ * Token to retrieve the next page.
+ *
+ * @generated from field: string next_page_token = 3;
+ */
+ nextPageToken = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.ListRoleMembersResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "members", kind: "message", T: RoleMembership, repeated: true },
+ { no: 3, name: "next_page_token", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): ListRoleMembersResponse {
+ return new ListRoleMembersResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): ListRoleMembersResponse {
+ return new ListRoleMembersResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): ListRoleMembersResponse {
+ return new ListRoleMembersResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: ListRoleMembersResponse | PlainMessage | undefined, b: ListRoleMembersResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(ListRoleMembersResponse, a, b);
+ }
+}
+
+/**
+ * RoleMembership is the role membership.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.RoleMembership
+ */
+export class RoleMembership extends Message {
+ /**
+ * The name of the principal assigned to the role.
+ *
+ * @generated from field: string principal = 1;
+ */
+ principal = "";
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.RoleMembership";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "principal", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): RoleMembership {
+ return new RoleMembership().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): RoleMembership {
+ return new RoleMembership().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): RoleMembership {
+ return new RoleMembership().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: RoleMembership | PlainMessage | undefined, b: RoleMembership | PlainMessage | undefined): boolean {
+ return proto3.util.equals(RoleMembership, a, b);
+ }
+}
+
+/**
+ * UpdateRoleMembershipRequest is the request to UpdateRoleMembership.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest
+ */
+export class UpdateRoleMembershipRequest extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ /**
+ * Create the role if it doesn't already exist.
+ * If the role is created in this way, the “add” list will be respected, but the “remove” list will be ignored.
+ *
+ * @generated from field: bool create = 2;
+ */
+ create = false;
+
+ /**
+ * Members to assign to the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership add = 3;
+ */
+ add: RoleMembership[] = [];
+
+ /**
+ * Members to remove from the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership remove = 4;
+ */
+ remove: RoleMembership[] = [];
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.UpdateRoleMembershipRequest";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "create", kind: "scalar", T: 8 /* ScalarType.BOOL */ },
+ { no: 3, name: "add", kind: "message", T: RoleMembership, repeated: true },
+ { no: 4, name: "remove", kind: "message", T: RoleMembership, repeated: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): UpdateRoleMembershipRequest {
+ return new UpdateRoleMembershipRequest().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): UpdateRoleMembershipRequest {
+ return new UpdateRoleMembershipRequest().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): UpdateRoleMembershipRequest {
+ return new UpdateRoleMembershipRequest().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: UpdateRoleMembershipRequest | PlainMessage | undefined, b: UpdateRoleMembershipRequest | PlainMessage | undefined): boolean {
+ return proto3.util.equals(UpdateRoleMembershipRequest, a, b);
+ }
+}
+
+/**
+ * UpdateRoleMembershipResponse is the response for UpdateRoleMembership.
+ *
+ * @generated from message redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse
+ */
+export class UpdateRoleMembershipResponse extends Message {
+ /**
+ * The role name.
+ *
+ * @generated from field: string role_name = 1;
+ */
+ roleName = "";
+
+ /**
+ * Members assigned to the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership added = 2;
+ */
+ added: RoleMembership[] = [];
+
+ /**
+ * Members removed from the role.
+ *
+ * @generated from field: repeated redpanda.api.console.v1alpha1.RoleMembership removed = 3;
+ */
+ removed: RoleMembership[] = [];
+
+ constructor(data?: PartialMessage) {
+ super();
+ proto3.util.initPartial(data, this);
+ }
+
+ static readonly runtime: typeof proto3 = proto3;
+ static readonly typeName = "redpanda.api.console.v1alpha1.UpdateRoleMembershipResponse";
+ static readonly fields: FieldList = proto3.util.newFieldList(() => [
+ { no: 1, name: "role_name", kind: "scalar", T: 9 /* ScalarType.STRING */ },
+ { no: 2, name: "added", kind: "message", T: RoleMembership, repeated: true },
+ { no: 3, name: "removed", kind: "message", T: RoleMembership, repeated: true },
+ ]);
+
+ static fromBinary(bytes: Uint8Array, options?: Partial): UpdateRoleMembershipResponse {
+ return new UpdateRoleMembershipResponse().fromBinary(bytes, options);
+ }
+
+ static fromJson(jsonValue: JsonValue, options?: Partial): UpdateRoleMembershipResponse {
+ return new UpdateRoleMembershipResponse().fromJson(jsonValue, options);
+ }
+
+ static fromJsonString(jsonString: string, options?: Partial): UpdateRoleMembershipResponse {
+ return new UpdateRoleMembershipResponse().fromJsonString(jsonString, options);
+ }
+
+ static equals(a: UpdateRoleMembershipResponse | PlainMessage | undefined, b: UpdateRoleMembershipResponse | PlainMessage | undefined): boolean {
+ return proto3.util.equals(UpdateRoleMembershipResponse, a, b);
+ }
+}
+
diff --git a/frontend/src/state/backendApi.ts b/frontend/src/state/backendApi.ts
index 45d192e7b..210b741c4 100644
--- a/frontend/src/state/backendApi.ts
+++ b/frontend/src/state/backendApi.ts
@@ -21,47 +21,94 @@ import { ObjToKv } from '../utils/tsxUtils';
import { decodeBase64, TimeSince } from '../utils/utils';
import { appGlobal } from './appGlobal';
import {
- GetAclsRequest, AclRequestDefault, GetAclOverviewResponse, AdminInfo,
- AlterConfigOperation, AlterPartitionReassignmentsResponse, ApiError,
- Broker, BrokerConfigResponse, ClusterAdditionalInfo, ClusterConnectors,
- ClusterInfo, ClusterInfoResponse, ConfigEntry, ConfigResourceType,
- ConnectorValidationResult, CreateTopicRequest, CreateTopicResponse,
- DeleteConsumerGroupOffsetsRequest, DeleteConsumerGroupOffsetsResponse,
- DeleteConsumerGroupOffsetsResponseTopic, DeleteConsumerGroupOffsetsTopic,
- DeleteRecordsResponseData, EditConsumerGroupOffsetsRequest,
- EditConsumerGroupOffsetsResponse, EditConsumerGroupOffsetsResponseTopic,
- EditConsumerGroupOffsetsTopic, EndpointCompatibility,
- EndpointCompatibilityResponse, GetAllPartitionsResponse,
- GetConsumerGroupResponse, GetConsumerGroupsResponse, GetPartitionsResponse,
- GetTopicConsumersResponse, GetTopicOffsetsByTimestampResponse,
- GetTopicsResponse, GroupDescription, isApiError, KafkaConnectors,
- PartialTopicConfigsResponse, Partition, PartitionReassignmentRequest,
- PartitionReassignments, PartitionReassignmentsResponse,
- PatchConfigsRequest, PatchConfigsResponse, ProduceRecordsResponse,
- PublishRecordsRequest, QuotaResponse, ResourceConfig,
- Topic, TopicConfigResponse, TopicConsumer, TopicDescription,
- TopicDocumentation, TopicDocumentationResponse, TopicOffset,
- TopicPermissions, UserData, WrappedApiError, CreateACLRequest,
- DeleteACLsRequest, RedpandaLicense, AclResource, GetUsersResponse, CreateUserRequest,
- PatchTopicConfigsRequest, CreateSecretResponse, ClusterOverview, BrokerWithConfigAndStorage,
+ AclRequestDefault,
+ AclResource,
+ AdminInfo,
+ AlterConfigOperation,
+ AlterPartitionReassignmentsResponse,
+ ApiError,
+ Broker,
+ BrokerConfigResponse,
+ BrokerWithConfigAndStorage,
+ ClusterAdditionalInfo,
+ ClusterConnectors,
+ ClusterInfo,
+ ClusterInfoResponse,
+ ClusterOverview,
+ CompressionType,
+ ConfigEntry,
+ ConfigResourceType,
+ ConnectorValidationResult,
+ CreateACLRequest,
+ CreateSecretResponse,
+ CreateTopicRequest,
+ CreateTopicResponse,
+ CreateUserRequest,
+ DeleteACLsRequest,
+ DeleteConsumerGroupOffsetsRequest,
+ DeleteConsumerGroupOffsetsResponse,
+ DeleteConsumerGroupOffsetsResponseTopic,
+ DeleteConsumerGroupOffsetsTopic,
+ DeleteRecordsResponseData,
+ EditConsumerGroupOffsetsRequest,
+ EditConsumerGroupOffsetsResponse,
+ EditConsumerGroupOffsetsResponseTopic,
+ EditConsumerGroupOffsetsTopic,
+ EndpointCompatibility,
+ EndpointCompatibilityResponse,
+ GetAclOverviewResponse,
+ GetAclsRequest,
+ GetAllPartitionsResponse,
+ GetConsumerGroupResponse,
+ GetConsumerGroupsResponse,
+ GetPartitionsResponse,
+ GetTopicConsumersResponse,
+ GetTopicOffsetsByTimestampResponse,
+ GetTopicsResponse,
+ GetUsersResponse,
+ GroupDescription,
+ isApiError,
+ KafkaConnectors,
OverviewNewsEntry,
+ PartialTopicConfigsResponse,
+ Partition,
+ PartitionReassignmentRequest,
+ PartitionReassignments,
+ PartitionReassignmentsResponse,
+ PatchConfigsRequest,
+ PatchConfigsResponse,
+ PatchTopicConfigsRequest,
Payload,
- SchemaRegistrySubject,
- SchemaRegistrySubjectDetails,
- SchemaRegistryModeResponse,
+ ProduceRecordsResponse,
+ PublishRecordsRequest,
+ QuotaResponse,
+ RedpandaLicense,
+ ResourceConfig,
+ SchemaReferencedByEntry,
+ SchemaRegistryCompatibilityMode,
SchemaRegistryConfigResponse,
- SchemaRegistrySchemaTypesResponse,
- SchemaRegistryCreateSchemaResponse,
SchemaRegistryCreateSchema,
- SchemaRegistryDeleteSubjectVersionResponse,
+ SchemaRegistryCreateSchemaResponse,
SchemaRegistryDeleteSubjectResponse,
- SchemaRegistryCompatibilityMode,
+ SchemaRegistryDeleteSubjectVersionResponse,
+ SchemaRegistryModeResponse,
+ SchemaRegistrySchemaTypesResponse,
SchemaRegistrySetCompatibilityModeRequest,
- SchemaReferencedByEntry,
+ SchemaRegistrySubject,
+ SchemaRegistrySubjectDetails,
SchemaRegistryValidateSchemaResponse,
SchemaVersion,
+ Topic,
+ TopicConfigResponse,
+ TopicConsumer,
+ TopicDescription,
+ TopicDocumentation,
+ TopicDocumentationResponse,
TopicMessage,
- CompressionType
+ TopicOffset,
+ TopicPermissions,
+ UserData,
+ WrappedApiError
} from './restInterfaces';
import { uiState } from './uiState';
import { config as appConfig, isEmbedded } from '../config';
@@ -69,9 +116,10 @@ import { createStandaloneToast, redpandaTheme, redpandaToastOptions } from '@red
import { proto3 } from '@bufbuild/protobuf';
import { ListMessagesRequest } from '../protogen/redpanda/api/console/v1alpha1/list_messages_pb';
-import { PayloadEncoding, CompressionType as ProtoCompressionType } from '../protogen/redpanda/api/console/v1alpha1/common_pb';
+import { CompressionType as ProtoCompressionType, PayloadEncoding } from '../protogen/redpanda/api/console/v1alpha1/common_pb';
import { PublishMessageRequest, PublishMessageResponse } from '../protogen/redpanda/api/console/v1alpha1/publish_messages_pb';
import { PartitionOffsetOrigin } from './ui';
+import { Features } from './supportedFeatures';
const REST_TIMEOUT_SEC = 25;
export const REST_CACHE_DURATION_SEC = 20;
@@ -1486,6 +1534,109 @@ const apiStore = {
};
+export type RolePrincipal = { name: string, principalType: 'User' };
+export const rolesApi = observable({
+ roles: [] as string[],
+ roleMembers: new Map(), // RoleName -> Principals
+
+ async refreshRoles(): Promise {
+ const client = appConfig.securityClient;
+ if (!client) throw new Error('security client is not initialized');
+
+ const roles: string[] = [];
+
+ if (Features.rolesApi) {
+ let nextPageToken = '';
+ while (true) {
+ const res = await client.listRoles({ pageSize: 500, pageToken: nextPageToken });
+
+ const newRoles = res.roles.map(x => x.name);
+ roles.push(...newRoles);
+
+ if (!res.nextPageToken || res.nextPageToken.length == 0)
+ break;
+
+ nextPageToken = res.nextPageToken;
+ }
+ }
+
+ this.roles = roles;
+ },
+
+ async refreshRoleMembers() {
+ const client = appConfig.securityClient;
+ if (!client) throw new Error('security client is not initialized');
+
+ const rolePromises = [];
+
+ if (Features.rolesApi) {
+ for (const role of this.roles) {
+ rolePromises.push(client.getRole({ roleName: role }));
+ }
+ }
+
+ await Promise.allSettled(rolePromises);
+
+ this.roleMembers.clear();
+
+ for (const r of rolePromises) {
+ const res = await r;
+ if (res.role == null) continue; // how could this ever happen, maybe someone deleted the role right before we retreived the members?
+ const roleName = res.role.name;
+
+ const members = res.members.map(x => {
+
+ const principalParts = x.principal.split(':');
+ if (principalParts.length != 2) {
+ console.error('failed to split principal of role', { roleName, principal: x.principal });
+ return null;
+ }
+ const principalType = principalParts[0];
+ const name = principalParts[1];
+
+ if (principalType != 'User') {
+ console.error('unexpected principal type in refreshRoleMembers', { roleName, principal: x.principal });
+ }
+
+ return { principalType, name } as RolePrincipal;
+ }).filterNull();
+
+ this.roleMembers.set(roleName, members);
+ }
+ },
+
+ async createRole(name: string) {
+ const client = appConfig.securityClient;
+ if (!client) throw new Error('security client is not initialized');
+
+ if (Features.rolesApi) {
+ await client.createRole({ role: { name } });
+ }
+ },
+
+ async deleteRole(name: string, deleteAcls: boolean) {
+ const client = appConfig.securityClient;
+ if (!client) throw new Error('security client is not initialized');
+
+ if (Features.rolesApi) {
+ await client.deleteRole({ roleName: name, deleteAcls });
+ }
+ },
+
+ async updateRoleMembership(roleName: string, addUsers: string[], removeUsers: string[], create = false) {
+ const client = appConfig.securityClient;
+ if (!client) throw new Error('security client is not initialized');
+
+ return await client.updateRoleMembership({
+ roleName: roleName,
+ add: addUsers.map(u => ({ principal: 'User:' + u })),
+ remove: removeUsers.map(u => ({ principal: 'User:' + u })),
+ create,
+ });
+ },
+});
+
+
export function createMessageSearch() {
const messageSearch = {
// Parameters last passed to 'startMessageSearch'
diff --git a/frontend/src/state/restInterfaces.ts b/frontend/src/state/restInterfaces.ts
index ae2fbf397..4ba9cab36 100644
--- a/frontend/src/state/restInterfaces.ts
+++ b/frontend/src/state/restInterfaces.ts
@@ -692,6 +692,7 @@ export type AclStrResourceType =
| 'Cluster'
| 'TransactionalID'
| 'DelegationToken'
+ | 'RedpandaRole'
;
export type AclStrResourcePatternType =
diff --git a/frontend/src/state/supportedFeatures.ts b/frontend/src/state/supportedFeatures.ts
index b3659bb01..bb20b4102 100644
--- a/frontend/src/state/supportedFeatures.ts
+++ b/frontend/src/state/supportedFeatures.ts
@@ -37,6 +37,7 @@ export class Feature {
static readonly GetQuotas: FeatureEntry = { endpoint: '/api/quotas', method: 'GET' };
static readonly CreateUser: FeatureEntry = { endpoint: '/api/users', method: 'POST' };
static readonly DeleteUser: FeatureEntry = { endpoint: '/api/users', method: 'DELETE' };
+ static readonly SecurityService: FeatureEntry = { endpoint: 'redpanda.api.console.v1alpha1.SecurityService', method: 'POST' };
}
export function isSupported(f: FeatureEntry): boolean {
@@ -48,6 +49,10 @@ export function isSupported(f: FeatureEntry): boolean {
if (e.endpoint == f.endpoint)
return e.isSupported;
+ // Special handling, this will be completely absent in the community version
+ if (f.endpoint.includes('.SecurityService'))
+ return false;
+
featureErrors.push(`Unable to check if feature "${f.method} ${f.endpoint}" is supported because the backend did not return any information about it.`);
return false;
}
@@ -72,6 +77,7 @@ class SupportedFeatures {
@computed get getQuotas(): boolean { return isSupported(Feature.GetQuotas); }
@computed get createUser(): boolean { return isSupported(Feature.CreateUser); }
@computed get deleteUser(): boolean { return isSupported(Feature.DeleteUser); }
+ @computed get rolesApi(): boolean { return isSupported(Feature.SecurityService); }
}
const features = new SupportedFeatures();
diff --git a/frontend/src/state/ui.ts b/frontend/src/state/ui.ts
index eea131df6..b5961ba36 100644
--- a/frontend/src/state/ui.ts
+++ b/frontend/src/state/ui.ts
@@ -233,6 +233,15 @@ const defaultUiSettings = {
},
aclList: {
+ usersTab: {
+ quickSearch: '',
+ pageSize: 20,
+ },
+ rolesTab: {
+ quickSearch: '',
+ pageSize: 20,
+ },
+
configTable: {
quickSearch: '',
pageSize: 20,
diff --git a/proto/gen/openapi/openapi.json b/proto/gen/openapi/openapi.json
index 727856edc..818b80b86 100644
--- a/proto/gen/openapi/openapi.json
+++ b/proto/gen/openapi/openapi.json
@@ -1 +1 @@
-{"components":{"schemas":{"ACL.Operation":{"description":"The operation that is allowed or denied (e.g. READ).","enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"},"BadRequest":{"description":"Describes violations in a client request. This error type focuses on the\nsyntactic aspects of the request.","properties":{"field_violations":{"description":"Describes all violations in a client request.","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"title":"BadRequest","type":"object"},"Config":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConfigAlterOperation":{"enum":["CONFIG_ALTER_OPERATION_SET","CONFIG_ALTER_OPERATION_DELETE","CONFIG_ALTER_OPERATION_APPEND","CONFIG_ALTER_OPERATION_SUBTRACT"],"type":"string"},"ConfigSource":{"enum":["CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG","CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG","CONFIG_SOURCE_STATIC_BROKER_CONFIG","CONFIG_SOURCE_DEFAULT_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG"],"type":"string"},"ConfigSynonym":{"properties":{"name":{"type":"string"},"source":{"$ref":"#/components/schemas/ConfigSource"},"value":{"nullable":true,"type":"string"}},"type":"object"},"ConfigType":{"enum":["CONFIG_TYPE_BOOLEAN","CONFIG_TYPE_STRING","CONFIG_TYPE_INT","CONFIG_TYPE_SHORT","CONFIG_TYPE_LONG","CONFIG_TYPE_DOUBLE","CONFIG_TYPE_LIST","CONFIG_TYPE_CLASS","CONFIG_TYPE_PASSWORD"],"type":"string"},"Configuration":{"properties":{"config_synonyms":{"description":"If no config value is set at the topic level, it will inherit the value\nset at the broker or cluster level. `name` is the corresponding config\nkey whose value is inherited. `source` indicates whether the inherited\nconfig is default, broker, etc.","items":{"$ref":"#/components/schemas/ConfigSynonym"},"type":"array"},"documentation":{"description":"Config documentation.","nullable":true,"type":"string"},"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"read_only":{"description":"Whether the config is read-only, or is dynamic and can be altered.","type":"boolean"},"sensitive":{"description":"Whether this is a sensitive config key and value.","type":"boolean"},"source":{"$ref":"#/components/schemas/ConfigSource"},"type":{"$ref":"#/components/schemas/ConfigType"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConnectCluster":{"properties":{"address":{"type":"string"},"info":{"$ref":"#/components/schemas/ConnectCluster.Info"},"name":{"type":"string"},"plugins":{"items":{"$ref":"#/components/schemas/ConnectorPlugin"},"type":"array"}},"type":"object"},"ConnectCluster.Info":{"properties":{"commit":{"type":"string"},"kafka_cluster_id":{"type":"string"},"version":{"type":"string"}},"type":"object"},"Connector":{"properties":{"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"ConnectorError":{"properties":{"content":{"type":"string"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ConnectorError.Type"}},"title":"ConnectorError is the error of a connector, this is holistic error\nabstraction, made parsing the error trace of connector or Task","type":"object"},"ConnectorError.Type":{"enum":["TYPE_ERROR","TYPE_WARNING"],"type":"string"},"ConnectorHolisticState":{"description":"- CONNECTOR_HOLISTIC_STATE_PAUSED: PAUSED: The connector/task has been administratively paused.\n - CONNECTOR_HOLISTIC_STATE_RESTARTING: RESTARTING: he connector/task is restarting.\n - CONNECTOR_HOLISTIC_STATE_DESTROYED: DESTROYED: Connector is in destroyed state, regardless of any tasks.\n - CONNECTOR_HOLISTIC_STATE_STOPPED: STOPPED: The connector/task has been stopped.\n - CONNECTOR_HOLISTIC_STATE_UNASSIGNED: The connector/task has not yet been assigned to a worker\nUNASSIGNED: Connector is in unassigned state.\n Or Connector is in running state, and there are unassigned tasks.\n - CONNECTOR_HOLISTIC_STATE_HEALTHY: HEALTHY: Connector is in running state, \u003e 0 tasks, all of them in running state.\n - CONNECTOR_HOLISTIC_STATE_UNHEALTHY: UNHEALTHY: Connector is failed state.\n\t\t\tOr Connector is in running state but has 0 tasks.\n\t\t\tOr Connector is in running state, has \u003e 0 tasks, and all tasks are in failed state.\n - CONNECTOR_HOLISTIC_STATE_DEGRADED: DEGRADED: Connector is in running state, has \u003e 0 tasks, but has at least one state in failed state, but not all tasks are failed.\n - CONNECTOR_HOLISTIC_STATE_UNKNOWN: UNKNOWN: The connector/task could no be determined","enum":["CONNECTOR_HOLISTIC_STATE_PAUSED","CONNECTOR_HOLISTIC_STATE_RESTARTING","CONNECTOR_HOLISTIC_STATE_DESTROYED","CONNECTOR_HOLISTIC_STATE_STOPPED","CONNECTOR_HOLISTIC_STATE_UNASSIGNED","CONNECTOR_HOLISTIC_STATE_HEALTHY","CONNECTOR_HOLISTIC_STATE_UNHEALTHY","CONNECTOR_HOLISTIC_STATE_DEGRADED","CONNECTOR_HOLISTIC_STATE_UNKNOWN"],"title":"The following states are possible for a connector or one of its tasks\nimplement the state interface described in the Kafka connect API @see\nhttps://docs.confluent.io/platform/current/connect/monitoring.html#connector-and-task-status\nthis includes holistic unified connector status that takes into account not\njust the connector instance state, but also state of all the tasks within the\nconnector","type":"string"},"ConnectorInfoStatus":{"properties":{"info":{"$ref":"#/components/schemas/ConnectorSpec"},"name":{"title":"name is the connector name","type":"string"},"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"ConnectorPlugin":{"properties":{"class":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"}},"type":"object"},"ConnectorSpec":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskInfo"},"readOnly":true,"type":"array"},"type":{"readOnly":true,"type":"string"}},"required":["name","config"],"title":"ConectorInfo is the spec of the connector, as defined in the Kafka connect\nAPI, it can be used as input of the connector creation or output of the\nconnectors","type":"object"},"ConnectorStatus":{"properties":{"connector":{"$ref":"#/components/schemas/Connector"},"errors":{"items":{"$ref":"#/components/schemas/ConnectorError"},"title":"Errors is list of parsed connectors' and tasks' errors","type":"array"},"holistic_state":{"$ref":"#/components/schemas/ConnectorHolisticState"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskStatus"},"type":"array"},"type":{"type":"string"}},"type":"object"},"CreateACLRequest":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.\nFor requests with resource_type CLUSTER, this will default to \"kafka-cluster\".","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","principal","host","operation","permission_type"],"type":"object"},"CreateACLResponse":{"type":"object"},"CreateConnectSecretResponse":{"description":"CreateConnectSecretResponse is the response of CreateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"CreateConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"CreateTopicRequest.Topic":{"properties":{"configs":{"description":"An array of key-value config pairs for a topic.\nThese correspond to Kafka topic-level configs.","items":{"$ref":"#/components/schemas/Config"},"type":"array"},"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions to give the topic. If specifying\npartitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default partition count, set to null.","format":"int32","nullable":true,"type":"integer"},"replica_assignments":{"description":"Manually specify broker ID assignments for partition replicas. If manually assigning replicas, both `replication_factor` and\n`partition_count` must be -1.","items":{"$ref":"#/components/schemas/ReplicaAssignment"},"type":"array"},"replication_factor":{"description":"The number of replicas every partition must have.\nIf specifying partitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default replication factor, set to null.","format":"int32","nullable":true,"type":"integer"}},"type":"object"},"CreateTopicResponse":{"properties":{"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions created for the topic.\nThis field has a default value of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"},"replication_factor":{"description":"The number of replicas per topic partition.\nThis field has a default of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"}},"type":"object"},"CreateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"CreateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/CreateUserResponse.User"}},"type":"object"},"CreateUserResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"title":"Name of newly-created user","type":"string"}},"type":"object"},"DeleteACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","operation","permission_type"],"type":"object"},"DeleteACLsResponse":{"properties":{"matching_acls":{"items":{"$ref":"#/components/schemas/MatchingACL"},"type":"array"}},"type":"object"},"DeleteConnectSecretResponse":{"description":"DeleteConnectSecretResponse is the response of DeleteConnectSecret.","type":"object"},"DeleteTopicResponse":{"type":"object"},"DeleteTransformResponse":{"type":"object"},"DeleteUserResponse":{"type":"object"},"DeployTransformRequest":{"description":"Metadata required to deploy a new Wasm\ntransform in a Redpanda cluster.","properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"The input topic to apply the transform to.","example":"orders","type":"string"},"name":{"description":"Name of the transform.","example":"redact-payment-details-in-orders","type":"string"},"output_topic_names":{"description":"Output topic to write the transform results to.","example":"orders-redacted","items":{"type":"string"},"type":"array"}},"required":["name","input_topic_name","output_topic_names"],"type":"object"},"EnvironmentVariable":{"properties":{"key":{"description":"The key of your environment variable.","example":"LOG_LEVEL","type":"string"},"value":{"description":"The value of your environment variable.","example":"DEBUG","type":"string"}},"required":["key","value"],"type":"object"},"ErrorInfo":{"description":"Describes the cause of the error with structured details.\n\nExample of an error when contacting the \"pubsub.googleapis.com\" API when it\nis not enabled:\n\n { \"reason\": \"API_DISABLED\"\n \"domain\": \"googleapis.com\"\n \"metadata\": {\n \"resource\": \"projects/123\",\n \"service\": \"pubsub.googleapis.com\"\n }\n }\n\nThis response indicates that the pubsub.googleapis.com API is not enabled.\n\nExample of an error that is returned when attempting to create a Spanner\ninstance in a region that is out of stock:\n\n { \"reason\": \"STOCKOUT\"\n \"domain\": \"spanner.googleapis.com\",\n \"metadata\": {\n \"availableRegions\": \"us-central1,us-east2\"\n }\n }","properties":{"domain":{"description":"The logical grouping to which the \"reason\" belongs. The error domain\nis typically the registered service name of the tool or product that\ngenerates the error. Example: \"pubsub.googleapis.com\". If the error is\ngenerated by some common infrastructure, the error domain must be a\nglobally unique value that identifies the infrastructure. For Google API\ninfrastructure, the error domain is \"googleapis.com\".","type":"string"},"metadata":{"additionalProperties":{"type":"string"},"description":"Additional structured details about this error.\n\nKeys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in\nlength. When identifying the current value of an exceeded limit, the units\nshould be contained in the key, not the value. For example, rather than\n{\"instanceLimit\": \"100/request\"}, should be returned as,\n{\"instanceLimitPerRequest\": \"100\"}, if the client exceeds the number of\ninstances that can be created in a single (batch) request.","type":"object"},"reason":{"description":"The reason of the error. This is a constant value that identifies the\nproximate cause of the error. Error reasons are unique within a particular\ndomain of errors. This should be at most 63 characters and match a\nregular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents\nUPPER_SNAKE_CASE.","type":"string"}},"title":"ErrorInfo","type":"object"},"FieldViolation":{"description":"A message type used to describe a single bad request field.","properties":{"description":{"description":"A description of why the request element is bad.","type":"string"},"field":{"description":"A path that leads to a field in the request body. The value will be a\nsequence of dot-separated identifiers that identify a protocol buffer\nfield.\n\nConsider the following:\n\n message CreateContactRequest {\n message EmailAddress {\n enum Type {\n TYPE_UNSPECIFIED = 0;\n HOME = 1;\n WORK = 2;\n }\n\n optional string email = 1;\n repeated EmailType type = 2;\n }\n\n string full_name = 1;\n repeated EmailAddress email_addresses = 2;\n }\n\nIn this example, in proto `field` could take one of the following values:\n\n* `full_name` for a violation in the `full_name` value\n* `email_addresses[1].email` for a violation in the `email` field of the\n first `email_addresses` message\n* `email_addresses[3].type[2]` for a violation in the second `type`\n value in the third `email_addresses` message.\n\nIn JSON, the same values are represented as:\n\n* `fullName` for a violation in the `fullName` value\n* `emailAddresses[1].email` for a violation in the `email` field of the\n first `emailAddresses` message\n* `emailAddresses[3].type[2]` for a violation in the second `type`\n value in the third `emailAddresses` message.","type":"string"}},"type":"object"},"GetConnectClusterResponse":{"properties":{"cluster":{"$ref":"#/components/schemas/ConnectCluster"}},"type":"object"},"GetConnectSecretResponse":{"description":"GetConnectSecretResponse is the response of GetConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"GetConnectorConfigResponse":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"GetConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"GetConnectorStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"GetTopicConfigurationsResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"GetTransformResponse":{"properties":{"transform":{"$ref":"#/components/schemas/TransformMetadata"}},"type":"object"},"Help":{"description":"Provides links to documentation or for performing an out of band action.\n\nFor example, if a quota check failed with an error indicating the calling\nproject hasn't enabled the accessed service, this can contain a URL pointing\ndirectly to the right place in the developer console to flip the bit.","properties":{"links":{"description":"URL(s) pointing to additional information on handling the current error.","items":{"$ref":"#/components/schemas/Link"},"type":"array"}},"title":"Help","type":"object"},"Link":{"description":"Describes a URL link.","properties":{"description":{"description":"Describes what the link offers.","type":"string"},"url":{"description":"The URL of the link.","type":"string"}},"type":"object"},"ListACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ListACLsResponse":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"type":"object"},"ListConnectClustersResponse":{"properties":{"clusters":{"items":{"$ref":"#/components/schemas/ConnectCluster"},"type":"array"}},"type":"object"},"ListConnectSecretsResponse":{"description":"ListConnectSecretsResponse is the response of ListConnectSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListConnectorTopicsResponse":{"properties":{"topics":{"items":{"type":"string"},"type":"array"}},"type":"object"},"ListConnectorsResponse":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/ConnectorInfoStatus"},"title":"connectors is the list of connectors the key is the connector name","type":"array"},"next_page_token":{"description":"Page Token to fetch the next page. The value can be used as page_token in the next call to this endpoint.","type":"string"}},"type":"object"},"ListSecretsFilter":{"description":"ListSecretsFilter are the filter options for listing secrets.","properties":{"labels[string]":{"additionalProperties":{"type":"string"},"description":"The secret labels to search for.","type":"object"},"name_contains":{"description":"Substring match on secret name. Case-sensitive.","type":"string"}},"type":"object"},"ListSecretsResponse":{"description":"ListSecretsResponse is the response of ListSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListTopicsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on topic name. Case-sensitive.","type":"string"}},"type":"object"},"ListTopicsResponse":{"properties":{"next_page_token":{"type":"string"},"topics":{"items":{"$ref":"#/components/schemas/ListTopicsResponse.Topic"},"type":"array"}},"type":"object"},"ListTopicsResponse.Topic":{"properties":{"internal":{"description":"Whether topic is internal only.","type":"boolean"},"name":{"description":"Topic name.","type":"string"},"partition_count":{"description":"Topic partition count.","format":"int32","type":"integer"},"replication_factor":{"description":"Topic replication factor.","format":"int32","type":"integer"}},"type":"object"},"ListTransformsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on transform name. Case-sensitive.","type":"string"}},"type":"object"},"ListTransformsResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"transforms":{"items":{"$ref":"#/components/schemas/TransformMetadata"},"type":"array"}},"type":"object"},"ListUsersRequest.Filter":{"properties":{"name":{"description":"Username.","type":"string"},"name_contains":{"description":"Substring match on username. Case-sensitive.","type":"string"}},"type":"object"},"ListUsersResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"users":{"items":{"$ref":"#/components/schemas/ListUsersResponse.User"},"type":"array"}},"type":"object"},"ListUsersResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"}},"type":"object"},"MatchingACL":{"properties":{"error":{"$ref":"#/components/schemas/Status"},"host":{"description":"The host address to for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"Options":{"properties":{"include_tasks":{"type":"boolean"},"only_failed":{"type":"boolean"}},"type":"object"},"PartitionStatus":{"enum":["PARTITION_STATUS_RUNNING","PARTITION_STATUS_INACTIVE","PARTITION_STATUS_ERRORED","PARTITION_STATUS_UNKNOWN"],"type":"string"},"PartitionTransformStatus":{"properties":{"broker_id":{"format":"int32","type":"integer"},"lag":{"format":"int32","type":"integer"},"partition_id":{"format":"int32","type":"integer"},"status":{"$ref":"#/components/schemas/PartitionStatus"}},"type":"object"},"PermissionType":{"description":"Whether the operation should be allowed or denied.","enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"},"Policy":{"properties":{"host":{"description":"The host address for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"}},"type":"object"},"QuotaFailure":{"description":"Describes how a quota check failed.\n\nFor example if a daily limit was exceeded for the calling project,\na service could respond with a QuotaFailure detail containing the project\nid and the description of the quota limit that was exceeded. If the\ncalling project hasn't enabled the service in the developer console, then\na service could respond with the project id and set `service_disabled`\nto true.\n\nAlso see RetryInfo and Help types for other details about handling a\nquota failure.","properties":{"violations":{"description":"Describes all quota violations.","items":{"$ref":"#/components/schemas/QuotaFailure.Violation"},"type":"array"}},"title":"QuotaFailure","type":"object"},"QuotaFailure.Violation":{"description":"A message type used to describe a single quota violation. For example, a\ndaily quota or a custom quota that was exceeded.","properties":{"description":{"description":"A description of how the quota check failed. Clients can use this\ndescription to find more about the quota configuration in the service's\npublic documentation, or find the relevant quota limit to adjust through\ndeveloper console.\n\nFor example: \"Service disabled\" or \"Daily Limit for read operations\nexceeded\".","type":"string"},"subject":{"description":"The subject on which the quota check failed.\nFor example, \"clientip:\u003cip address of client\u003e\" or \"project:\u003cGoogle\ndeveloper project id\u003e\".","type":"string"}},"type":"object"},"ReplicaAssignment":{"properties":{"partition_id":{"description":"A partition to create.","format":"int32","type":"integer"},"replica_ids":{"description":"The broker IDs the partition replicas are assigned to.","items":{"format":"int32","type":"integer"},"type":"array"}},"type":"object"},"Resource":{"properties":{"acls":{"items":{"$ref":"#/components/schemas/Policy"},"type":"array"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ResourcePatternType":{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"},"ResourceType":{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"},"SASLMechanism":{"description":"SASL mechanism to use for authentication.","enum":["SASL_MECHANISM_SCRAM_SHA_256","SASL_MECHANISM_SCRAM_SHA_512"],"type":"string"},"Secret":{"description":"Defines the secret resource.","properties":{"id":{"description":"Secret identifier.","readOnly":true,"type":"string"},"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"}},"type":"object"},"SetConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"SetTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after this update.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"Status":{"description":"The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors).","properties":{"code":{"description":"RPC status code, as described [here](https://github.com/googleapis/googleapis/blob/b4c238feaa1097c53798ed77035bbfeb7fc72e96/google/rpc/code.proto#L32).","enum":["OK","CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","UNAUTHENTICATED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS"],"format":"int32","type":"string"},"details":{"items":{"description":"Details of the error.","oneOf":[{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.BadRequest"],"type":"string"}}},{"$ref":"#/components/schemas/BadRequest"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.ErrorInfo"],"type":"string"}}},{"$ref":"#/components/schemas/ErrorInfo"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.QuotaFailure"],"type":"string"}}},{"$ref":"#/components/schemas/QuotaFailure"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.Help"],"type":"string"}}},{"$ref":"#/components/schemas/Help"}]}]},"type":"array"},"message":{"description":"Detailed error message. No compatibility guarantees are given for the text contained in this message.","type":"string"}},"type":"object"},"TaskInfo":{"properties":{"connector":{"type":"string"},"task":{"format":"int32","type":"integer"}},"type":"object"},"TaskStatus":{"properties":{"id":{"format":"int32","type":"integer"},"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"TransformMetadata":{"properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"Input topic to apply the transform to.","type":"string"},"name":{"description":"Name of transform.","type":"string"},"output_topic_names":{"description":"Output topics to write the transform results to.","items":{"type":"string"},"type":"array"},"statuses":{"items":{"$ref":"#/components/schemas/PartitionTransformStatus"},"type":"array"}},"type":"object"},"UpdateConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"operation":{"$ref":"#/components/schemas/ConfigAlterOperation"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"UpdateConnectSecretResponse":{"description":"UpdateConnectSecretResponse is the response of UpdateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"UpdateTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after applying this partial patch.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"UpdateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"UpdateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/UpdateUserResponse.User"}},"type":"object"},"UpdateUserResponse.User":{"description":"Updated user's name and SASL mechanism.","properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"type":"string"}},"type":"object"},"UpsertConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"v1alpha1.Topic":{"type":"object"}},"securitySchemes":{"auth0":{"description":"RedpandaCloud","flows":{"implicit":{"authorizationUrl":"https://prod-cloudv2.us.auth0.com/oauth/authorize","scopes":{},"x-client-id":"dQjapNIAHhF7EQqQToRla3yEII9sUSap"}},"type":"oauth2"}}},"info":{"description":"Welcome to Redpanda Cloud's Dataplane API documentation.","title":"Redpanda Cloud","version":"v1alpha1"},"openapi":"3.0.3","paths":{"/v1alpha1/acls":{"delete":{"description":"Delete all ACLs that match the filter criteria. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_DeleteACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","required":true,"schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","required":true,"schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","required":true,"schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","required":true,"schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete ACLs","tags":["ACLService"]},"get":{"description":"List all ACLs. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_ListACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List ACLs","tags":["ACLService"]},"post":{"description":"Create a new ACL.","operationId":"ACLService_CreateACL","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLRequest"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLResponse"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create ACL","tags":["ACLService"]}},"/v1alpha1/connect/clusters":{"get":{"description":"List connect clusters available for being consumed by the console's kafka-connect service.","operationId":"KafkaConnectService_ListConnectClusters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectClustersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connect clusters","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}":{"get":{"description":"Get information about an available Kafka Connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","operationId":"KafkaConnectService_GetConnectCluster","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectCluster"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Connect cluster not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connect cluster","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors":{"get":{"description":"List connectors managed by the Kafka Connect service.","operationId":"KafkaConnectService_ListConnectors","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connectors","tags":["KafkaConnectService"]},"post":{"description":"Create a connector with the specified configuration.","operationId":"KafkaConnectService_CreateConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"required":true,"x-originalParamName":"connector"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}":{"delete":{"description":"Delete a connector. This operation force stops all tasks and also deletes the connector configuration.","operationId":"KafkaConnectService_DeleteConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete connector","tags":["KafkaConnectService"]},"get":{"description":"Get information about a connector in a specific cluster.","operationId":"KafkaConnectService_GetConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/config":{"get":{"description":"Get the configuration for the connector.","operationId":"KafkaConnectService_GetConnectorConfig","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector configuration","tags":["KafkaConnectService"]},"put":{"description":"Create a new connector using the given configuration, or update the configuration for an existing connector. Returns information about the connector after the change has been made.","operationId":"KafkaConnectService_UpsertConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"required":["config"],"type":"object"}}},"required":true,"x-originalParamName":"config"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Updated"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Upsert connector configuration","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/pause":{"put":{"description":"Pause the connector and its tasks, which stops messages from processing until the connector is resumed. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_PauseConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Pause request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Pause connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/restart":{"post":{"description":"Restarts a connector, triggers a connector restart, you can specify the only_failed or/and the include_tasks options.","operationId":"KafkaConnectService_RestartConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Options"}}},"required":true,"x-originalParamName":"options"},"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Restart connector request success"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Restart connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/resume":{"put":{"description":"Resume a paused connector and its tasks, and resumes message processing. This call is asynchronous and may take some time to process. If the connector was not paused, this operation does not do anything.","operationId":"KafkaConnectService_ResumeConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Resume request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Resume connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/status":{"get":{"description":"Gets the current status of the connector, including the state for each of its tasks, error information, etc.","operationId":"KafkaConnectService_GetConnectorStatus","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorStatus"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector status","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/stop":{"put":{"description":"Stops a connector, but does not delete the connector. All tasks for the connector are shut down completely. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_StopConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Stop connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics":{"get":{"description":"Returns a list of connector topic names. If the connector is inactive, this call returns an empty list.","operationId":"KafkaConnectService_ListConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics/reset":{"put":{"description":"Resets the set of topic names that the connector is using.","operationId":"KafkaConnectService_ResetConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Reset connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets":{"get":{"description":"List Kafka Connect cluster secrets. Optional: filter based on secret name and labels.","operationId":"SecretService_ListConnectSecrets","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Substring match on secret name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"This is a request variable of the map type. The query format is \"map_name[key]=value\", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age[\"bob\"]=18","in":"query","name":"filter.labels[string]","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecretsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Connect Cluster Secrets","tags":["SecretService"]},"post":{"description":"Create a Kafka Connect cluster secret.","operationId":"SecretService_CreateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"CreateConnectSecretRequest is the request of CreateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"name":{"description":"Name of connector.","type":"string"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["name","secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"Secret created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets/{id}":{"delete":{"description":"Delete a Kafka Connect cluster secret.","operationId":"SecretService_DeleteConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to delete.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Secret deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Connect Cluster Secret","tags":["SecretService"]},"get":{"description":"Get a specific Kafka Connect cluster secret.","operationId":"SecretService_GetConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"The ID of the secret to retrieve.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Connect Cluster Secret","tags":["SecretService"]},"put":{"description":"Update a Kafka Connect cluster secret.","operationId":"SecretService_UpdateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to update.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"UpdateConnectSecretRequest is the request of UpdateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/topics":{"get":{"description":"List topics, with partition count and replication factor. Optional: filter based on topic name.","operationId":"TopicService_ListTopics","parameters":[{"description":"Substring match on topic name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Topics","tags":["TopicService"]},"post":{"description":"Create a topic.","operationId":"TopicService_CreateTopic","parameters":[{"description":"If true, makes this request a dry run; everything is validated but\nno topics are actually created.","in":"query","name":"validate_only","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTopicRequest.Topic"}}},"description":"The topic to create.","required":true,"x-originalParamName":"topic"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1alpha1.Topic"}}},"description":"Topic created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Topic","tags":["TopicService"]}},"/v1alpha1/topics/{name}":{"delete":{"description":"Delete the Kafka topic with the requested name.","operationId":"TopicService_DeleteTopic","parameters":[{"description":"Topic name.","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Topic deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Requested topic does not exist"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Topic","tags":["TopicService"]}},"/v1alpha1/topics/{topic_name}/configurations":{"get":{"description":"Get key-value configs for a topic.","operationId":"TopicService_GetTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Topic Configurations","tags":["TopicService"]},"patch":{"description":"Update a subset of the topic configurations.","operationId":"TopicService_UpdateTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UpdateConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Topic Configuration","tags":["TopicService"]},"put":{"description":"Update the entire set of key-value configurations for a topic. Config entries that are not provided in the request are removed and will fall back to their default values.","operationId":"TopicService_SetTopicConfigurations","parameters":[{"description":"Name of topic.","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SetConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Set Topic Configurations","tags":["TopicService"]}},"/v1alpha1/transforms":{"get":{"description":"Retrieve a list of Wasm transforms. Optional: filter based on transform name.","operationId":"TransformService_ListTransforms","parameters":[{"description":"Substring match on transform name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","transforms":[{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic11","output-topic12"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]},{"environment_variables":[],"input_topic_name":"topic2","name":"transform2","output_topic_names":["output-topic21","output-topic22"],"statuses":[{"broker_id":2,"lag":2,"partition_id":2,"status":"PARTITION_STATUS_RUNNING"}]}]},"schema":{"$ref":"#/components/schemas/ListTransformsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Transforms","tags":["TransformService"]},"put":{"description":"Initiate deployment of a new Wasm transform. This endpoint uses multipart/form-data encoding. Following deployment, a brief period is required before the Wasm transform becomes operational. Monitor the partition statuses to check whether the transform is active. This usually takes around 3s, but no longer than 10s.","operationId":"TransformService_DeployTransform","requestBody":{"content":{"multipart/form-data":{"schema":{"example":"{\"name\":\"redact-orders\",\"input_topic_name\":\"orders\",\"output_topic_names\":[\"orders-redacted\"],\"environment_variables\":[{\"key\":\"LOGGER_LEVEL\",\"value\":\"DEBUG\"}]}","properties":{"metadata":{"$ref":"#/components/schemas/DeployTransformRequest"},"wasm_binary":{"description":"Binary file containing the compiled WASM transform. The maximum size for this file is 10MiB.","format":"binary","type":"string"}},"type":"object"}}},"description":"Transform metadata as well as the WASM binary","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransformMetadata"}}},"description":"Created"}},"summary":"Deploy Transform","tags":["TransformService"]}},"/v1alpha1/transforms/{name}":{"delete":{"description":"Delete a Wasm transform with the requested name.","operationId":"TransformService_DeleteTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"Transform deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Transform","tags":["TransformService"]},"get":{"description":"Get a specific Wasm transform.","operationId":"TransformService_GetTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"transform":{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic1","output-topic2"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]}},"schema":{"$ref":"#/components/schemas/GetTransformResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Transform","tags":["TransformService"]}},"/v1alpha1/users":{"get":{"description":"List users. Optional: filter based on username.","operationId":"UserService_ListUsers","parameters":[{"description":"Username.","in":"query","name":"filter.name","schema":{"type":"string"}},{"description":"Substring match on username. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","users":[{"name":"payment-service"},{"name":"jane"}]},"schema":{"$ref":"#/components/schemas/ListUsersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Users","tags":["UserService"]},"post":{"description":"Create a new user.","operationId":"UserService_CreateUser","requestBody":{"content":{"application/json":{"example":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service","password":"secure-password"},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"required":true,"x-originalParamName":"user"},"responses":{"201":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"description":"User created"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create User","tags":["UserService"]}},"/v1alpha1/users/{name}":{"delete":{"description":"Delete the specified user","operationId":"UserService_DeleteUser","parameters":[{"description":"Username","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"User deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"example":{"code":"NOT_FOUND","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_RESOURCE_NOT_FOUND"}],"message":"user not found"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete User","tags":["UserService"]}},"/v1alpha1/users/{user.name}":{"put":{"description":"Update a user's credentials.","operationId":"UserService_UpdateUser","parameters":[{"description":"Username.","in":"path","name":"user.name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","password":"new-password"}},"schema":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"password":{"description":"Password.","type":"string"}},"type":"object"}}},"required":true,"x-originalParamName":"user"},"responses":{"200":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/UpdateUserResponse.User"}}},"description":"OK"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update User","tags":["UserService"]}}},"security":[{"auth0":[]}],"servers":[{"description":"Dataplane API","url":"{dataplane_api_url}","variables":{"dataplane_api_url":{"default":"https://api-a4cb21.ck09ma3c4vs12cng3cig.fmc.prd.cloud.redpanda.com","description":"Dataplane API.\u003cbr\u003e\n\t\t\t\t\tThe Dataplane API allows management of Topics,ACLs,Service accounts. It is exposed by each individual cluster.\n\t\t\t\t\tRetrieve the Dataplane API URL of a cluster by using the dataplane_api.url field returned by the Get Cluster endpoint.\u003cbr\u003e\u003cbr\u003e\n\t\t\t\t\tExample (Dedicated): https://api-a4cb21.ck09mi9c4vs17hng9gig.fmc.prd.cloud.redpanda.com\u003cbr\u003e\n\t\t\t\t\tExample (BYOC): https://api-a4cb21.ck09mi9c4vs17hng9gig.byoc.prd.cloud.redpanda.com"}}}],"tags":[{"name":"ACLService"},{"name":"TransformService"},{"name":"KafkaConnectService"},{"name":"SecretService"},{"name":"TopicService"},{"name":"UserService"}]}
\ No newline at end of file
+{"components":{"schemas":{"ACL.Operation":{"description":"The operation that is allowed or denied (e.g. READ).","enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"},"BadRequest":{"description":"Describes violations in a client request. This error type focuses on the\nsyntactic aspects of the request.","properties":{"field_violations":{"description":"Describes all violations in a client request.","items":{"$ref":"#/components/schemas/FieldViolation"},"type":"array"}},"title":"BadRequest","type":"object"},"Config":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConfigAlterOperation":{"enum":["CONFIG_ALTER_OPERATION_SET","CONFIG_ALTER_OPERATION_DELETE","CONFIG_ALTER_OPERATION_APPEND","CONFIG_ALTER_OPERATION_SUBTRACT"],"type":"string"},"ConfigSource":{"enum":["CONFIG_SOURCE_DYNAMIC_TOPIC_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_CONFIG","CONFIG_SOURCE_DYNAMIC_DEFAULT_BROKER_CONFIG","CONFIG_SOURCE_STATIC_BROKER_CONFIG","CONFIG_SOURCE_DEFAULT_CONFIG","CONFIG_SOURCE_DYNAMIC_BROKER_LOGGER_CONFIG"],"type":"string"},"ConfigSynonym":{"properties":{"name":{"type":"string"},"source":{"$ref":"#/components/schemas/ConfigSource"},"value":{"nullable":true,"type":"string"}},"type":"object"},"ConfigType":{"enum":["CONFIG_TYPE_BOOLEAN","CONFIG_TYPE_STRING","CONFIG_TYPE_INT","CONFIG_TYPE_SHORT","CONFIG_TYPE_LONG","CONFIG_TYPE_DOUBLE","CONFIG_TYPE_LIST","CONFIG_TYPE_CLASS","CONFIG_TYPE_PASSWORD"],"type":"string"},"Configuration":{"properties":{"config_synonyms":{"description":"If no config value is set at the topic level, it will inherit the value\nset at the broker or cluster level. `name` is the corresponding config\nkey whose value is inherited. `source` indicates whether the inherited\nconfig is default, broker, etc.","items":{"$ref":"#/components/schemas/ConfigSynonym"},"type":"array"},"documentation":{"description":"Config documentation.","nullable":true,"type":"string"},"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"read_only":{"description":"Whether the config is read-only, or is dynamic and can be altered.","type":"boolean"},"sensitive":{"description":"Whether this is a sensitive config key and value.","type":"boolean"},"source":{"$ref":"#/components/schemas/ConfigSource"},"type":{"$ref":"#/components/schemas/ConfigType"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"ConnectCluster":{"properties":{"address":{"type":"string"},"info":{"$ref":"#/components/schemas/ConnectCluster.Info"},"name":{"type":"string"},"plugins":{"items":{"$ref":"#/components/schemas/ConnectorPlugin"},"type":"array"}},"type":"object"},"ConnectCluster.Info":{"properties":{"commit":{"type":"string"},"kafka_cluster_id":{"type":"string"},"version":{"type":"string"}},"type":"object"},"Connector":{"properties":{"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"ConnectorError":{"properties":{"content":{"type":"string"},"title":{"type":"string"},"type":{"$ref":"#/components/schemas/ConnectorError.Type"}},"title":"ConnectorError is the error of a connector, this is holistic error\nabstraction, made parsing the error trace of connector or Task","type":"object"},"ConnectorError.Type":{"enum":["TYPE_ERROR","TYPE_WARNING"],"type":"string"},"ConnectorHolisticState":{"description":"- CONNECTOR_HOLISTIC_STATE_PAUSED: PAUSED: The connector/task has been administratively paused.\n - CONNECTOR_HOLISTIC_STATE_RESTARTING: RESTARTING: he connector/task is restarting.\n - CONNECTOR_HOLISTIC_STATE_DESTROYED: DESTROYED: Connector is in destroyed state, regardless of any tasks.\n - CONNECTOR_HOLISTIC_STATE_STOPPED: STOPPED: The connector/task has been stopped.\n - CONNECTOR_HOLISTIC_STATE_UNASSIGNED: The connector/task has not yet been assigned to a worker\nUNASSIGNED: Connector is in unassigned state.\n Or Connector is in running state, and there are unassigned tasks.\n - CONNECTOR_HOLISTIC_STATE_HEALTHY: HEALTHY: Connector is in running state, \u003e 0 tasks, all of them in running state.\n - CONNECTOR_HOLISTIC_STATE_UNHEALTHY: UNHEALTHY: Connector is failed state.\n\t\t\tOr Connector is in running state but has 0 tasks.\n\t\t\tOr Connector is in running state, has \u003e 0 tasks, and all tasks are in failed state.\n - CONNECTOR_HOLISTIC_STATE_DEGRADED: DEGRADED: Connector is in running state, has \u003e 0 tasks, but has at least one state in failed state, but not all tasks are failed.\n - CONNECTOR_HOLISTIC_STATE_UNKNOWN: UNKNOWN: The connector/task could no be determined","enum":["CONNECTOR_HOLISTIC_STATE_PAUSED","CONNECTOR_HOLISTIC_STATE_RESTARTING","CONNECTOR_HOLISTIC_STATE_DESTROYED","CONNECTOR_HOLISTIC_STATE_STOPPED","CONNECTOR_HOLISTIC_STATE_UNASSIGNED","CONNECTOR_HOLISTIC_STATE_HEALTHY","CONNECTOR_HOLISTIC_STATE_UNHEALTHY","CONNECTOR_HOLISTIC_STATE_DEGRADED","CONNECTOR_HOLISTIC_STATE_UNKNOWN"],"title":"The following states are possible for a connector or one of its tasks\nimplement the state interface described in the Kafka connect API @see\nhttps://docs.confluent.io/platform/current/connect/monitoring.html#connector-and-task-status\nthis includes holistic unified connector status that takes into account not\njust the connector instance state, but also state of all the tasks within the\nconnector","type":"string"},"ConnectorInfoStatus":{"properties":{"info":{"$ref":"#/components/schemas/ConnectorSpec"},"name":{"title":"name is the connector name","type":"string"},"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"ConnectorPlugin":{"properties":{"class":{"type":"string"},"type":{"type":"string"},"version":{"type":"string"}},"type":"object"},"ConnectorSpec":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskInfo"},"readOnly":true,"type":"array"},"type":{"readOnly":true,"type":"string"}},"required":["name","config"],"title":"ConectorInfo is the spec of the connector, as defined in the Kafka connect\nAPI, it can be used as input of the connector creation or output of the\nconnectors","type":"object"},"ConnectorStatus":{"properties":{"connector":{"$ref":"#/components/schemas/Connector"},"errors":{"items":{"$ref":"#/components/schemas/ConnectorError"},"title":"Errors is list of parsed connectors' and tasks' errors","type":"array"},"holistic_state":{"$ref":"#/components/schemas/ConnectorHolisticState"},"name":{"type":"string"},"tasks":{"items":{"$ref":"#/components/schemas/TaskStatus"},"type":"array"},"type":{"type":"string"}},"type":"object"},"CreateACLRequest":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.\nFor requests with resource_type CLUSTER, this will default to \"kafka-cluster\".","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","principal","host","operation","permission_type"],"type":"object"},"CreateACLResponse":{"type":"object"},"CreateConnectSecretResponse":{"description":"CreateConnectSecretResponse is the response of CreateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"CreateConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"CreateTopicRequest.Topic":{"properties":{"configs":{"description":"An array of key-value config pairs for a topic.\nThese correspond to Kafka topic-level configs.","items":{"$ref":"#/components/schemas/Config"},"type":"array"},"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions to give the topic. If specifying\npartitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default partition count, set to null.","format":"int32","nullable":true,"type":"integer"},"replica_assignments":{"description":"Manually specify broker ID assignments for partition replicas. If manually assigning replicas, both `replication_factor` and\n`partition_count` must be -1.","items":{"$ref":"#/components/schemas/ReplicaAssignment"},"type":"array"},"replication_factor":{"description":"The number of replicas every partition must have.\nIf specifying partitions manually (see `replica_assignments`), set to -1.\nOr, to use the cluster default replication factor, set to null.","format":"int32","nullable":true,"type":"integer"}},"type":"object"},"CreateTopicResponse":{"properties":{"name":{"description":"Name of topic.","type":"string"},"partition_count":{"description":"The number of partitions created for the topic.\nThis field has a default value of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"},"replication_factor":{"description":"The number of replicas per topic partition.\nThis field has a default of -1, which may be returned if the broker\ndoes not support v5+ of this request which added support for returning\nthis information.","format":"int32","type":"integer"}},"type":"object"},"CreateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"CreateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/CreateUserResponse.User"}},"type":"object"},"CreateUserResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"title":"Name of newly-created user","type":"string"}},"type":"object"},"DeleteACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"required":["resource_type","resource_pattern_type","operation","permission_type"],"type":"object"},"DeleteACLsResponse":{"properties":{"matching_acls":{"items":{"$ref":"#/components/schemas/MatchingACL"},"type":"array"}},"type":"object"},"DeleteConnectSecretResponse":{"description":"DeleteConnectSecretResponse is the response of DeleteConnectSecret.","type":"object"},"DeleteTopicResponse":{"type":"object"},"DeleteTransformResponse":{"type":"object"},"DeleteUserResponse":{"type":"object"},"DeployTransformRequest":{"description":"Metadata required to deploy a new Wasm\ntransform in a Redpanda cluster.","properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"The input topic to apply the transform to.","example":"orders","type":"string"},"name":{"description":"Name of the transform.","example":"redact-payment-details-in-orders","type":"string"},"output_topic_names":{"description":"Output topic to write the transform results to.","example":"orders-redacted","items":{"type":"string"},"type":"array"}},"required":["name","input_topic_name","output_topic_names"],"type":"object"},"EnvironmentVariable":{"properties":{"key":{"description":"The key of your environment variable.","example":"LOG_LEVEL","type":"string"},"value":{"description":"The value of your environment variable.","example":"DEBUG","type":"string"}},"required":["key","value"],"type":"object"},"ErrorInfo":{"description":"Describes the cause of the error with structured details.\n\nExample of an error when contacting the \"pubsub.googleapis.com\" API when it\nis not enabled:\n\n { \"reason\": \"API_DISABLED\"\n \"domain\": \"googleapis.com\"\n \"metadata\": {\n \"resource\": \"projects/123\",\n \"service\": \"pubsub.googleapis.com\"\n }\n }\n\nThis response indicates that the pubsub.googleapis.com API is not enabled.\n\nExample of an error that is returned when attempting to create a Spanner\ninstance in a region that is out of stock:\n\n { \"reason\": \"STOCKOUT\"\n \"domain\": \"spanner.googleapis.com\",\n \"metadata\": {\n \"availableRegions\": \"us-central1,us-east2\"\n }\n }","properties":{"domain":{"description":"The logical grouping to which the \"reason\" belongs. The error domain\nis typically the registered service name of the tool or product that\ngenerates the error. Example: \"pubsub.googleapis.com\". If the error is\ngenerated by some common infrastructure, the error domain must be a\nglobally unique value that identifies the infrastructure. For Google API\ninfrastructure, the error domain is \"googleapis.com\".","type":"string"},"metadata":{"additionalProperties":{"type":"string"},"description":"Additional structured details about this error.\n\nKeys should match /[a-zA-Z0-9-_]/ and be limited to 64 characters in\nlength. When identifying the current value of an exceeded limit, the units\nshould be contained in the key, not the value. For example, rather than\n{\"instanceLimit\": \"100/request\"}, should be returned as,\n{\"instanceLimitPerRequest\": \"100\"}, if the client exceeds the number of\ninstances that can be created in a single (batch) request.","type":"object"},"reason":{"description":"The reason of the error. This is a constant value that identifies the\nproximate cause of the error. Error reasons are unique within a particular\ndomain of errors. This should be at most 63 characters and match a\nregular expression of `[A-Z][A-Z0-9_]+[A-Z0-9]`, which represents\nUPPER_SNAKE_CASE.","type":"string"}},"title":"ErrorInfo","type":"object"},"FieldViolation":{"description":"A message type used to describe a single bad request field.","properties":{"description":{"description":"A description of why the request element is bad.","type":"string"},"field":{"description":"A path that leads to a field in the request body. The value will be a\nsequence of dot-separated identifiers that identify a protocol buffer\nfield.\n\nConsider the following:\n\n message CreateContactRequest {\n message EmailAddress {\n enum Type {\n TYPE_UNSPECIFIED = 0;\n HOME = 1;\n WORK = 2;\n }\n\n optional string email = 1;\n repeated EmailType type = 2;\n }\n\n string full_name = 1;\n repeated EmailAddress email_addresses = 2;\n }\n\nIn this example, in proto `field` could take one of the following values:\n\n* `full_name` for a violation in the `full_name` value\n* `email_addresses[1].email` for a violation in the `email` field of the\n first `email_addresses` message\n* `email_addresses[3].type[2]` for a violation in the second `type`\n value in the third `email_addresses` message.\n\nIn JSON, the same values are represented as:\n\n* `fullName` for a violation in the `fullName` value\n* `emailAddresses[1].email` for a violation in the `email` field of the\n first `emailAddresses` message\n* `emailAddresses[3].type[2]` for a violation in the second `type`\n value in the third `emailAddresses` message.","type":"string"}},"type":"object"},"GetConnectClusterResponse":{"properties":{"cluster":{"$ref":"#/components/schemas/ConnectCluster"}},"type":"object"},"GetConnectSecretResponse":{"description":"GetConnectSecretResponse is the response of GetConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"GetConnectorConfigResponse":{"properties":{"config":{"additionalProperties":{"type":"string"},"type":"object"}},"type":"object"},"GetConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"GetConnectorStatusResponse":{"properties":{"status":{"$ref":"#/components/schemas/ConnectorStatus"}},"type":"object"},"GetTopicConfigurationsResponse":{"properties":{"configurations":{"items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"GetTransformResponse":{"properties":{"transform":{"$ref":"#/components/schemas/TransformMetadata"}},"type":"object"},"Help":{"description":"Provides links to documentation or for performing an out of band action.\n\nFor example, if a quota check failed with an error indicating the calling\nproject hasn't enabled the accessed service, this can contain a URL pointing\ndirectly to the right place in the developer console to flip the bit.","properties":{"links":{"description":"URL(s) pointing to additional information on handling the current error.","items":{"$ref":"#/components/schemas/Link"},"type":"array"}},"title":"Help","type":"object"},"Link":{"description":"Describes a URL link.","properties":{"description":{"description":"Describes what the link offers.","type":"string"},"url":{"description":"The URL of the link.","type":"string"}},"type":"object"},"ListACLsRequest.Filter":{"properties":{"host":{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","nullable":true,"type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","nullable":true,"type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","nullable":true,"type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ListACLsResponse":{"properties":{"resources":{"items":{"$ref":"#/components/schemas/Resource"},"type":"array"}},"type":"object"},"ListConnectClustersResponse":{"properties":{"clusters":{"items":{"$ref":"#/components/schemas/ConnectCluster"},"type":"array"}},"type":"object"},"ListConnectSecretsResponse":{"description":"ListConnectSecretsResponse is the response of ListConnectSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListConnectorTopicsResponse":{"properties":{"topics":{"items":{"type":"string"},"type":"array"}},"type":"object"},"ListConnectorsResponse":{"properties":{"connectors":{"items":{"$ref":"#/components/schemas/ConnectorInfoStatus"},"title":"connectors is the list of connectors the key is the connector name","type":"array"},"next_page_token":{"description":"Page Token to fetch the next page. The value can be used as page_token in the next call to this endpoint.","type":"string"}},"type":"object"},"ListSecretsFilter":{"description":"ListSecretsFilter are the filter options for listing secrets.","properties":{"labels[string]":{"additionalProperties":{"type":"string"},"description":"The secret labels to search for.","type":"object"},"name_contains":{"description":"Substring match on secret name. Case-sensitive.","type":"string"}},"type":"object"},"ListSecretsResponse":{"description":"ListSecretsResponse is the response of ListSecrets.","properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"secrets":{"description":"Secrets retrieved.","items":{"$ref":"#/components/schemas/Secret"},"type":"array"}},"type":"object"},"ListTopicsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on topic name. Case-sensitive.","type":"string"}},"type":"object"},"ListTopicsResponse":{"properties":{"next_page_token":{"type":"string"},"topics":{"items":{"$ref":"#/components/schemas/ListTopicsResponse.Topic"},"type":"array"}},"type":"object"},"ListTopicsResponse.Topic":{"properties":{"internal":{"description":"Whether topic is internal only.","type":"boolean"},"name":{"description":"Topic name.","type":"string"},"partition_count":{"description":"Topic partition count.","format":"int32","type":"integer"},"replication_factor":{"description":"Topic replication factor.","format":"int32","type":"integer"}},"type":"object"},"ListTransformsRequest.Filter":{"properties":{"name_contains":{"description":"Substring match on transform name. Case-sensitive.","type":"string"}},"type":"object"},"ListTransformsResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"transforms":{"items":{"$ref":"#/components/schemas/TransformMetadata"},"type":"array"}},"type":"object"},"ListUsersRequest.Filter":{"properties":{"name":{"description":"Username.","type":"string"},"name_contains":{"description":"Substring match on username. Case-sensitive.","type":"string"}},"type":"object"},"ListUsersResponse":{"properties":{"next_page_token":{"description":"Token to retrieve the next page.","type":"string"},"users":{"items":{"$ref":"#/components/schemas/ListUsersResponse.User"},"type":"array"}},"type":"object"},"ListUsersResponse.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"}},"type":"object"},"MatchingACL":{"properties":{"error":{"$ref":"#/components/schemas/Status"},"host":{"description":"The host address to for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"Options":{"properties":{"include_tasks":{"type":"boolean"},"only_failed":{"type":"boolean"}},"type":"object"},"PartitionStatus":{"enum":["PARTITION_STATUS_RUNNING","PARTITION_STATUS_INACTIVE","PARTITION_STATUS_ERRORED","PARTITION_STATUS_UNKNOWN"],"type":"string"},"PartitionTransformStatus":{"properties":{"broker_id":{"format":"int32","type":"integer"},"lag":{"format":"int32","type":"integer"},"partition_id":{"format":"int32","type":"integer"},"status":{"$ref":"#/components/schemas/PartitionStatus"}},"type":"object"},"PermissionType":{"description":"Whether the operation should be allowed or denied.","enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"},"Policy":{"properties":{"host":{"description":"The host address for this ACL.","type":"string"},"operation":{"$ref":"#/components/schemas/ACL.Operation"},"permission_type":{"$ref":"#/components/schemas/PermissionType"},"principal":{"description":"The user for whom this ACL applies.","type":"string"}},"type":"object"},"QuotaFailure":{"description":"Describes how a quota check failed.\n\nFor example if a daily limit was exceeded for the calling project,\na service could respond with a QuotaFailure detail containing the project\nid and the description of the quota limit that was exceeded. If the\ncalling project hasn't enabled the service in the developer console, then\na service could respond with the project id and set `service_disabled`\nto true.\n\nAlso see RetryInfo and Help types for other details about handling a\nquota failure.","properties":{"violations":{"description":"Describes all quota violations.","items":{"$ref":"#/components/schemas/QuotaFailure.Violation"},"type":"array"}},"title":"QuotaFailure","type":"object"},"QuotaFailure.Violation":{"description":"A message type used to describe a single quota violation. For example, a\ndaily quota or a custom quota that was exceeded.","properties":{"description":{"description":"A description of how the quota check failed. Clients can use this\ndescription to find more about the quota configuration in the service's\npublic documentation, or find the relevant quota limit to adjust through\ndeveloper console.\n\nFor example: \"Service disabled\" or \"Daily Limit for read operations\nexceeded\".","type":"string"},"subject":{"description":"The subject on which the quota check failed.\nFor example, \"clientip:\u003cip address of client\u003e\" or \"project:\u003cGoogle\ndeveloper project id\u003e\".","type":"string"}},"type":"object"},"ReplicaAssignment":{"properties":{"partition_id":{"description":"A partition to create.","format":"int32","type":"integer"},"replica_ids":{"description":"The broker IDs the partition replicas are assigned to.","items":{"format":"int32","type":"integer"},"type":"array"}},"type":"object"},"Resource":{"properties":{"acls":{"items":{"$ref":"#/components/schemas/Policy"},"type":"array"},"resource_name":{"description":"The name of the resource this ACL targets.","type":"string"},"resource_pattern_type":{"$ref":"#/components/schemas/ResourcePatternType"},"resource_type":{"$ref":"#/components/schemas/ResourceType"}},"type":"object"},"ResourcePatternType":{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"},"ResourceType":{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"},"SASLMechanism":{"description":"SASL mechanism to use for authentication.","enum":["SASL_MECHANISM_SCRAM_SHA_256","SASL_MECHANISM_SCRAM_SHA_512"],"type":"string"},"Secret":{"description":"Defines the secret resource.","properties":{"id":{"description":"Secret identifier.","readOnly":true,"type":"string"},"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"}},"type":"object"},"SetConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"SetTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after this update.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"Status":{"description":"The `Status` type defines a logical error model that is suitable for\ndifferent programming environments, including REST APIs and RPC APIs. It is\nused by [gRPC](https://github.com/grpc). Each `Status` message contains\nthree pieces of data: error code, error message, and error details.\n\nYou can find out more about this error model and how to work with it in the\n[API Design Guide](https://cloud.google.com/apis/design/errors).","properties":{"code":{"description":"RPC status code, as described [here](https://github.com/googleapis/googleapis/blob/b4c238feaa1097c53798ed77035bbfeb7fc72e96/google/rpc/code.proto#L32).","enum":["OK","CANCELLED","UNKNOWN","INVALID_ARGUMENT","DEADLINE_EXCEEDED","NOT_FOUND","ALREADY_EXISTS","PERMISSION_DENIED","UNAUTHENTICATED","RESOURCE_EXHAUSTED","FAILED_PRECONDITION","ABORTED","OUT_OF_RANGE","UNIMPLEMENTED","INTERNAL","UNAVAILABLE","DATA_LOSS"],"format":"int32","type":"string"},"details":{"items":{"description":"Details of the error.","oneOf":[{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.BadRequest"],"type":"string"}}},{"$ref":"#/components/schemas/BadRequest"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.ErrorInfo"],"type":"string"}}},{"$ref":"#/components/schemas/ErrorInfo"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.QuotaFailure"],"type":"string"}}},{"$ref":"#/components/schemas/QuotaFailure"}]},{"allOf":[{"properties":{"@type":{"description":"Fully qualified protobuf type name of the underlying response, prefixed with `type.googleapis.com/`.","enum":["type.googleapis.com/google.rpc.Help"],"type":"string"}}},{"$ref":"#/components/schemas/Help"}]}]},"type":"array"},"message":{"description":"Detailed error message. No compatibility guarantees are given for the text contained in this message.","type":"string"}},"type":"object"},"TaskInfo":{"properties":{"connector":{"type":"string"},"task":{"format":"int32","type":"integer"}},"type":"object"},"TaskStatus":{"properties":{"id":{"format":"int32","type":"integer"},"state":{"type":"string"},"trace":{"type":"string"},"worker_id":{"type":"string"}},"type":"object"},"TransformMetadata":{"properties":{"environment_variables":{"description":"The environment variables you want to apply to your transform's environment","items":{"$ref":"#/components/schemas/EnvironmentVariable"},"type":"array"},"input_topic_name":{"description":"Input topic to apply the transform to.","type":"string"},"name":{"description":"Name of transform.","type":"string"},"output_topic_names":{"description":"Output topics to write the transform results to.","items":{"type":"string"},"type":"array"},"statuses":{"items":{"$ref":"#/components/schemas/PartitionTransformStatus"},"type":"array"}},"type":"object"},"UpdateConfiguration":{"properties":{"name":{"description":"A topic-level config key (e.g. `segment.bytes`).","type":"string"},"operation":{"$ref":"#/components/schemas/ConfigAlterOperation"},"value":{"description":"A topic-level config value (e.g. 1073741824).","nullable":true,"type":"string"}},"type":"object"},"UpdateConnectSecretResponse":{"description":"UpdateConnectSecretResponse is the response of UpdateConnectSecret.","properties":{"secret":{"$ref":"#/components/schemas/Secret"}},"type":"object"},"UpdateTopicConfigurationsResponse":{"properties":{"configurations":{"description":"Topic's complete set of configurations after applying this partial patch.","items":{"$ref":"#/components/schemas/Configuration"},"type":"array"}},"type":"object"},"UpdateUserRequest.User":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"description":"Username.","type":"string"},"password":{"description":"Password.","type":"string"}},"type":"object"},"UpdateUserResponse":{"properties":{"user":{"$ref":"#/components/schemas/UpdateUserResponse.User"}},"type":"object"},"UpdateUserResponse.User":{"description":"Updated user's name and SASL mechanism.","properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"name":{"type":"string"}},"type":"object"},"UpsertConnectorResponse":{"properties":{"connector":{"$ref":"#/components/schemas/ConnectorSpec"}},"type":"object"},"v1alpha1.Topic":{"type":"object"}},"securitySchemes":{"auth0":{"description":"RedpandaCloud","flows":{"implicit":{"authorizationUrl":"https://prod-cloudv2.us.auth0.com/oauth/authorize","scopes":{},"x-client-id":"dQjapNIAHhF7EQqQToRla3yEII9sUSap"}},"type":"oauth2"}}},"info":{"description":"Welcome to Redpanda Cloud's Dataplane API documentation.","title":"Redpanda Cloud","version":"v1alpha1"},"openapi":"3.0.3","paths":{"/v1alpha1/acls":{"delete":{"description":"Delete all ACLs that match the filter criteria. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_DeleteACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","required":true,"schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","required":true,"schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","required":true,"schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","required":true,"schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/DeleteACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete ACLs","tags":["ACLService"]},"get":{"description":"List all ACLs. The `filter.` query string parameters find matching ACLs that meet all specified conditions.","operationId":"ACLService_ListACLs","parameters":[{"description":"The type of resource (topic, consumer group, etc.) this\nACL targets.","in":"query","name":"filter.resource_type","schema":{"enum":["RESOURCE_TYPE_ANY","RESOURCE_TYPE_TOPIC","RESOURCE_TYPE_GROUP","RESOURCE_TYPE_CLUSTER","RESOURCE_TYPE_TRANSACTIONAL_ID","RESOURCE_TYPE_DELEGATION_TOKEN","RESOURCE_TYPE_USER"],"type":"string"}},{"description":"The name of the resource this ACL targets.","in":"query","name":"filter.resource_name","schema":{"type":"string"}},{"description":"The pattern to use for matching the specified resource_name\n(any, exact match, literal, or prefixed).","in":"query","name":"filter.resource_pattern_type","schema":{"enum":["RESOURCE_PATTERN_TYPE_ANY","RESOURCE_PATTERN_TYPE_MATCH","RESOURCE_PATTERN_TYPE_LITERAL","RESOURCE_PATTERN_TYPE_PREFIXED"],"type":"string"}},{"description":"The user for whom this ACL applies. With the Kafka simple\nauthorizer, you must include the prefix \"User:\" with the user name.","in":"query","name":"filter.principal","schema":{"type":"string"}},{"description":"The host address to use for this ACL. To allow a principal\naccess from multiple hosts, you must create an ACL for each host.","in":"query","name":"filter.host","schema":{"type":"string"}},{"description":"The operation that is allowed or denied (e.g. READ).","in":"query","name":"filter.operation","schema":{"enum":["OPERATION_ANY","OPERATION_ALL","OPERATION_READ","OPERATION_WRITE","OPERATION_CREATE","OPERATION_DELETE","OPERATION_ALTER","OPERATION_DESCRIBE","OPERATION_CLUSTER_ACTION","OPERATION_DESCRIBE_CONFIGS","OPERATION_ALTER_CONFIGS","OPERATION_IDEMPOTENT_WRITE","OPERATION_CREATE_TOKENS","OPERATION_DESCRIBE_TOKENS"],"type":"string"}},{"description":"Whether the operation should be allowed or denied.","in":"query","name":"filter.permission_type","schema":{"enum":["PERMISSION_TYPE_ANY","PERMISSION_TYPE_DENY","PERMISSION_TYPE_ALLOW"],"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListACLsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List ACLs","tags":["ACLService"]},"post":{"description":"Create a new ACL.","operationId":"ACLService_CreateACL","requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLRequest"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateACLResponse"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create ACL","tags":["ACLService"]}},"/v1alpha1/connect/clusters":{"get":{"description":"List connect clusters available for being consumed by the console's kafka-connect service.","operationId":"KafkaConnectService_ListConnectClusters","responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectClustersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connect clusters","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}":{"get":{"description":"Get information about an available Kafka Connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","operationId":"KafkaConnectService_GetConnectCluster","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectCluster"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Connect cluster not found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connect cluster","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors":{"get":{"description":"List connectors managed by the Kafka Connect service.","operationId":"KafkaConnectService_ListConnectors","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connectors","tags":["KafkaConnectService"]},"post":{"description":"Create a connector with the specified configuration.","operationId":"KafkaConnectService_CreateConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"required":true,"x-originalParamName":"connector"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}":{"delete":{"description":"Delete a connector. This operation force stops all tasks and also deletes the connector configuration.","operationId":"KafkaConnectService_DeleteConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Deleted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete connector","tags":["KafkaConnectService"]},"get":{"description":"Get information about a connector in a specific cluster.","operationId":"KafkaConnectService_GetConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/config":{"get":{"description":"Get the configuration for the connector.","operationId":"KafkaConnectService_GetConnectorConfig","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector configuration","tags":["KafkaConnectService"]},"put":{"description":"Create a new connector using the given configuration, or update the configuration for an existing connector. Returns information about the connector after the change has been made.","operationId":"KafkaConnectService_UpsertConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"additionalProperties":{"type":"string"},"required":["config"],"type":"object"}}},"required":true,"x-originalParamName":"config"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Updated"},"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorSpec"}}},"description":"Created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Upsert connector configuration","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/pause":{"put":{"description":"Pause the connector and its tasks, which stops messages from processing until the connector is resumed. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_PauseConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Pause request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Pause connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/restart":{"post":{"description":"Restarts a connector, triggers a connector restart, you can specify the only_failed or/and the include_tasks options.","operationId":"KafkaConnectService_RestartConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Options"}}},"required":true,"x-originalParamName":"options"},"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Restart connector request success"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Restart connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/resume":{"put":{"description":"Resume a paused connector and its tasks, and resumes message processing. This call is asynchronous and may take some time to process. If the connector was not paused, this operation does not do anything.","operationId":"KafkaConnectService_ResumeConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Resume request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Resume connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/status":{"get":{"description":"Gets the current status of the connector, including the state for each of its tasks, error information, etc.","operationId":"KafkaConnectService_GetConnectorStatus","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConnectorStatus"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get connector status","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/stop":{"put":{"description":"Stops a connector, but does not delete the connector. All tasks for the connector are shut down completely. This call is asynchronous and may take some time to process.","operationId":"KafkaConnectService_StopConnector","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"202":{"content":{"application/json":{"schema":{}}},"description":"Request accepted"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Stop connector","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics":{"get":{"description":"Returns a list of connector topic names. If the connector is inactive, this call returns an empty list.","operationId":"KafkaConnectService_ListConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListConnectorTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/connectors/{name}/topics/reset":{"put":{"description":"Resets the set of topic names that the connector is using.","operationId":"KafkaConnectService_ResetConnectorTopics","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Reset connector topics","tags":["KafkaConnectService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets":{"get":{"description":"List Kafka Connect cluster secrets. Optional: filter based on secret name and labels.","operationId":"SecretService_ListConnectSecrets","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"Substring match on secret name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"This is a request variable of the map type. The query format is \"map_name[key]=value\", e.g. If the map name is Age, the key type is string, and the value type is integer, the query parameter is expressed as Age[\"bob\"]=18","in":"query","name":"filter.labels[string]","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListSecretsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Connect Cluster Secrets","tags":["SecretService"]},"post":{"description":"Create a Kafka Connect cluster secret.","operationId":"SecretService_CreateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"CreateConnectSecretRequest is the request of CreateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"name":{"description":"Name of connector.","type":"string"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["name","secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"Secret created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/connect/clusters/{cluster_name}/secrets/{id}":{"delete":{"description":"Delete a Kafka Connect cluster secret.","operationId":"SecretService_DeleteConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to delete.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Secret deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Connect Cluster Secret","tags":["SecretService"]},"get":{"description":"Get a specific Kafka Connect cluster secret.","operationId":"SecretService_GetConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"The ID of the secret to retrieve.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Connect Cluster Secret","tags":["SecretService"]},"put":{"description":"Update a Kafka Connect cluster secret.","operationId":"SecretService_UpdateConnectSecret","parameters":[{"description":"Unique name of target connect cluster. For Redpanda self-hosted deployments, use the cluster name defined in the console's configuration file. For Redpanda Cloud, use `redpanda`.","in":"path","name":"cluster_name","required":true,"schema":{"type":"string"}},{"description":"ID of the secret to update.","in":"path","name":"id","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"description":"UpdateConnectSecretRequest is the request of UpdateConnectSecret.","properties":{"labels":{"additionalProperties":{"type":"string"},"description":"Secret labels.","type":"object"},"secret_data":{"description":"The secret data.","format":"byte","type":"string"}},"required":["secret_data"],"type":"object"}}},"required":true,"x-originalParamName":"body"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Secret"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Connect Cluster Secret","tags":["SecretService"]}},"/v1alpha1/topics":{"get":{"description":"List topics, with partition count and replication factor. Optional: filter based on topic name.","operationId":"TopicService_ListTopics","parameters":[{"description":"Substring match on topic name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response. If not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListTopicsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Topics","tags":["TopicService"]},"post":{"description":"Create a topic.","operationId":"TopicService_CreateTopic","parameters":[{"description":"If true, makes this request a dry run; everything is validated but\nno topics are actually created.","in":"query","name":"validate_only","schema":{"type":"boolean"}}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTopicRequest.Topic"}}},"description":"The topic to create.","required":true,"x-originalParamName":"topic"},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/v1alpha1.Topic"}}},"description":"Topic created"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create Topic","tags":["TopicService"]}},"/v1alpha1/topics/{name}":{"delete":{"description":"Delete the Kafka topic with the requested name.","operationId":"TopicService_DeleteTopic","parameters":[{"description":"Topic name.","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"schema":{}}},"description":"Topic deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Requested topic does not exist"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Topic","tags":["TopicService"]}},"/v1alpha1/topics/{topic_name}/configurations":{"get":{"description":"Get key-value configs for a topic.","operationId":"TopicService_GetTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Topic Configurations","tags":["TopicService"]},"patch":{"description":"Update a subset of the topic configurations.","operationId":"TopicService_UpdateTopicConfigurations","parameters":[{"description":"Topic name","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/UpdateConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update Topic Configuration","tags":["TopicService"]},"put":{"description":"Update the entire set of key-value configurations for a topic. Config entries that are not provided in the request are removed and will fall back to their default values.","operationId":"TopicService_SetTopicConfigurations","parameters":[{"description":"Name of topic.","in":"path","name":"topic_name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"schema":{"items":{"$ref":"#/components/schemas/SetConfiguration"},"type":"array"}}},"required":true,"x-originalParamName":"configurations"},"responses":{"200":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetTopicConfigurationsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Set Topic Configurations","tags":["TopicService"]}},"/v1alpha1/transforms":{"get":{"description":"Retrieve a list of Wasm transforms. Optional: filter based on transform name.","operationId":"TransformService_ListTransforms","parameters":[{"description":"Substring match on transform name. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","transforms":[{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic11","output-topic12"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]},{"environment_variables":[],"input_topic_name":"topic2","name":"transform2","output_topic_names":["output-topic21","output-topic22"],"statuses":[{"broker_id":2,"lag":2,"partition_id":2,"status":"PARTITION_STATUS_RUNNING"}]}]},"schema":{"$ref":"#/components/schemas/ListTransformsResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Transforms","tags":["TransformService"]},"put":{"description":"Initiate deployment of a new Wasm transform. This endpoint uses multipart/form-data encoding. Following deployment, a brief period is required before the Wasm transform becomes operational. Monitor the partition statuses to check whether the transform is active. This usually takes around 3s, but no longer than 10s.","operationId":"TransformService_DeployTransform","requestBody":{"content":{"multipart/form-data":{"schema":{"example":"{\"name\":\"redact-orders\", \"input_topic_name\":\"orders\", \"output_topic_names\":[\"orders-redacted\"], \"environment_variables\":[{\"key\":\"LOGGER_LEVEL\", \"value\":\"DEBUG\"}]}","properties":{"metadata":{"$ref":"#/components/schemas/DeployTransformRequest"},"wasm_binary":{"description":"Binary file containing the compiled WASM transform. The maximum size for this file is 10MiB.","format":"binary","type":"string"}},"type":"object"}}},"description":"Transform metadata as well as the WASM binary","required":true},"responses":{"201":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TransformMetadata"}}},"description":"Created"}},"summary":"Deploy Transform","tags":["TransformService"]}},"/v1alpha1/transforms/{name}":{"delete":{"description":"Delete a Wasm transform with the requested name.","operationId":"TransformService_DeleteTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"Transform deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete Transform","tags":["TransformService"]},"get":{"description":"Get a specific Wasm transform.","operationId":"TransformService_GetTransform","parameters":[{"description":"Name of transform.","example":{"name":"transform1"},"in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"transform":{"environment_variables":[],"input_topic_name":"topic1","name":"transform1","output_topic_names":["output-topic1","output-topic2"],"statuses":[{"broker_id":1,"lag":1,"partition_id":1,"status":"PARTITION_STATUS_RUNNING"}]}},"schema":{"$ref":"#/components/schemas/GetTransformResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Get Transform","tags":["TransformService"]}},"/v1alpha1/users":{"get":{"description":"List users. Optional: filter based on username.","operationId":"UserService_ListUsers","parameters":[{"description":"Username.","in":"query","name":"filter.name","schema":{"type":"string"}},{"description":"Substring match on username. Case-sensitive.","in":"query","name":"filter.name_contains","schema":{"type":"string"}},{"description":"Limit the paginated response to a number of items. Defaults to 100. Use -1 to disable pagination.","in":"query","name":"page_size","schema":{"format":"int32","type":"integer"}},{"description":"Value of the next_page_token field returned by the previous response.\nIf not provided, the system assumes the first page is requested.","in":"query","name":"page_token","schema":{"type":"string"}}],"responses":{"200":{"content":{"application/json":{"example":{"next_page_token":"","users":[{"name":"payment-service"},{"name":"jane"}]},"schema":{"$ref":"#/components/schemas/ListUsersResponse"}}},"description":"OK"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"List Users","tags":["UserService"]},"post":{"description":"Create a new user.","operationId":"UserService_CreateUser","requestBody":{"content":{"application/json":{"example":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service","password":"secure-password"},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"required":true,"x-originalParamName":"user"},"responses":{"201":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/CreateUserRequest.User"}}},"description":"User created"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Create User","tags":["UserService"]}},"/v1alpha1/users/{name}":{"delete":{"description":"Delete the specified user","operationId":"UserService_DeleteUser","parameters":[{"description":"Username","in":"path","name":"name","required":true,"schema":{"type":"string"}}],"responses":{"204":{"content":{"application/json":{"example":{},"schema":{}}},"description":"User deleted successfully"},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"404":{"content":{"application/json":{"example":{"code":"NOT_FOUND","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_RESOURCE_NOT_FOUND"}],"message":"user not found"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Not Found"},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Delete User","tags":["UserService"]}},"/v1alpha1/users/{user.name}":{"put":{"description":"Update a user's credentials.","operationId":"UserService_UpdateUser","parameters":[{"description":"Username.","in":"path","name":"user.name","required":true,"schema":{"type":"string"}}],"requestBody":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","password":"new-password"}},"schema":{"properties":{"mechanism":{"$ref":"#/components/schemas/SASLMechanism"},"password":{"description":"Password.","type":"string"}},"type":"object"}}},"required":true,"x-originalParamName":"user"},"responses":{"200":{"content":{"application/json":{"example":{"user":{"mechanism":"SASL_MECHANISM_SCRAM_SHA_256","name":"payment-service"}},"schema":{"$ref":"#/components/schemas/UpdateUserResponse.User"}}},"description":"OK"},"400":{"content":{"application/json":{"example":{"code":"INVALID_ARGUMENT","details":[{"@type":"type.googleapis.com/google.rpc.ErrorInfo","domain":"redpanda.com/dataplane","metadata":{},"reason":"REASON_INVALID_INPUT"},{"@type":"type.googleapis.com/google.rpc.BadRequest","field_violations":[{"description":"value length must be at least 3 characters","field":"user.password"},{"description":"value is required","field":"user.mechanism"}]}],"message":"provided parameters are invalid"},"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Bad request. Check API documentation and update request."},"401":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Unauthenticated."},"500":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"Internal Server Error. Reach out to support."},"default":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/Status"}}},"description":"An unexpected error response."}},"summary":"Update User","tags":["UserService"]}}},"security":[{"auth0":[]}],"servers":[{"description":"Dataplane API","url":"{dataplane_api_url}","variables":{"dataplane_api_url":{"default":"https://api-a4cb21.ck09ma3c4vs12cng3cig.fmc.prd.cloud.redpanda.com","description":"Dataplane API.\u003cbr\u003e\n\t\t\t\t\tThe Dataplane API allows management of Topics,ACLs,Service accounts. It is exposed by each individual cluster.\n\t\t\t\t\tRetrieve the Dataplane API URL of a cluster by using the dataplane_api.url field returned by the Get Cluster endpoint.\u003cbr\u003e\u003cbr\u003e\n\t\t\t\t\tExample (Dedicated): https://api-a4cb21.ck09mi9c4vs17hng9gig.fmc.prd.cloud.redpanda.com\u003cbr\u003e\n\t\t\t\t\tExample (BYOC): https://api-a4cb21.ck09mi9c4vs17hng9gig.byoc.prd.cloud.redpanda.com"}}}],"tags":[{"name":"ACLService"},{"name":"TransformService"},{"name":"KafkaConnectService"},{"name":"SecretService"},{"name":"TopicService"},{"name":"UserService"}]}
\ No newline at end of file
diff --git a/proto/gen/openapi/openapi.yaml b/proto/gen/openapi/openapi.yaml
index 75e8f3e41..826c88fba 100644
--- a/proto/gen/openapi/openapi.yaml
+++ b/proto/gen/openapi/openapi.yaml
@@ -2794,7 +2794,7 @@ paths:
content:
multipart/form-data:
schema:
- example: '{"name":"redact-orders","input_topic_name":"orders","output_topic_names":["orders-redacted"],"environment_variables":[{"key":"LOGGER_LEVEL","value":"DEBUG"}]}'
+ example: '{"name":"redact-orders", "input_topic_name":"orders", "output_topic_names":["orders-redacted"], "environment_variables":[{"key":"LOGGER_LEVEL", "value":"DEBUG"}]}'
properties:
metadata:
$ref: '#/components/schemas/DeployTransformRequest'
diff --git a/proto/redpanda/api/console/v1alpha1/security.proto b/proto/redpanda/api/console/v1alpha1/security.proto
new file mode 100644
index 000000000..f923c70ad
--- /dev/null
+++ b/proto/redpanda/api/console/v1alpha1/security.proto
@@ -0,0 +1,217 @@
+syntax = "proto3";
+
+package redpanda.api.console.v1alpha1;
+
+import "buf/validate/validate.proto";
+
+// Role defines a role in the system.
+message Role {
+ // The name of the role.
+ string name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+}
+
+// ListRolesRequest is the request for ListRoles.
+message ListRolesRequest {
+ // Filter options.
+ message Filter {
+ // Filter results only roles named with the prefix.
+ string name_prefix = 1 [
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^([a-zA-Z0-9-_]*)$"
+ ];
+
+ // Filter results to only roles with names which contain the string.
+ string name_contains = 2 [
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^([a-zA-Z0-9-_]*)$"
+ ];
+
+ // Return only roles assigned to this principal.
+ string principal = 3 [(buf.validate.field).string.max_len = 128];
+ }
+
+ // Optional filter.
+ optional Filter filter = 1;
+
+ // Page size.
+ int32 page_size = 2 [(buf.validate.field).int32 = {
+ gte: -1,
+ lte: 1000
+ }];
+
+ // Value of the next_page_token field returned by the previous response.
+ // If not provided, the system assumes the first page is requested.
+ string page_token = 3;
+}
+
+// ListRolesResponse is the response for ListRoles.
+message ListRolesResponse {
+ // The roles in the system.
+ repeated Role roles = 1;
+
+ // Token to retrieve the next page.
+ string next_page_token = 2;
+}
+
+// CreateRoleRequest is the request for CreateRole.
+message CreateRoleRequest {
+ // The role to create.
+ Role role = 1;
+}
+
+// CreateRoleResponse is the response for CreateRole.
+message CreateRoleResponse {
+ // The role.
+ Role role = 1;
+}
+
+// CreateRoleRequest is the request for CreateRole.
+message GetRoleRequest {
+ // The role name.
+ string role_name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+}
+
+// GetRoleResponse is the response to GetRole.
+message GetRoleResponse {
+ // The Role.
+ Role role = 1;
+
+ // Members assigned to the role.
+ repeated RoleMembership members = 2;
+}
+
+// DeleteRoleRequest is the request for DeleteRole.
+message DeleteRoleRequest {
+ // The role name.
+ string role_name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+
+ // Whether to delete the ACLs bound to the role.
+ bool delete_acls = 2;
+}
+
+// DeleteRoleResponse is the response for DeleteRole.
+message DeleteRoleResponse {}
+
+// List role members for a role. That is user principals assigned to that role.
+message ListRoleMembersRequest {
+ // Filter options.
+ message Filter {
+ // Filter results to only members with names which contain the string.
+ string name_contains = 1 [(buf.validate.field).string.max_len = 128];
+ }
+
+ // The role name.
+ string role_name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+
+ // Optional filter.
+ optional Filter filter = 2;
+
+ // Page size.
+ int32 page_size = 3 [(buf.validate.field).int32 = {
+ gte: -1,
+ lte: 1000
+ }];
+
+ // Value of the next_page_token field returned by the previous response.
+ // If not provided, the system assumes the first page is requested.
+ string page_token = 4;
+}
+
+// ListRoleMembersResponse is the response for ListRoleMembers.
+message ListRoleMembersResponse {
+ // The role name.
+ string role_name = 1;
+
+ // Members assigned to the role.
+ repeated RoleMembership members = 2;
+
+ // Token to retrieve the next page.
+ string next_page_token = 3;
+}
+
+// RoleMembership is the role membership.
+message RoleMembership {
+ // The name of the principal assigned to the role.
+ string principal = 1;
+}
+
+// UpdateRoleMembershipRequest is the request to UpdateRoleMembership.
+message UpdateRoleMembershipRequest {
+ // The role name.
+ string role_name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+
+ // Create the role if it doesn't already exist.
+ // If the role is created in this way, the “add” list will be respected, but the “remove” list will be ignored.
+ bool create = 2;
+
+ // Members to assign to the role.
+ repeated RoleMembership add = 3;
+
+ // Members to remove from the role.
+ repeated RoleMembership remove = 4;
+}
+
+// UpdateRoleMembershipResponse is the response for UpdateRoleMembership.
+message UpdateRoleMembershipResponse {
+ // The role name.
+ string role_name = 1 [
+ (buf.validate.field).required = true,
+ (buf.validate.field).string.min_len = 1,
+ (buf.validate.field).string.max_len = 128,
+ (buf.validate.field).string.pattern = "^[a-zA-Z0-9-_]+$"
+ ];
+
+ // Members assigned to the role.
+ repeated RoleMembership added = 2;
+
+ // Members removed from the role.
+ repeated RoleMembership removed = 3;
+}
+
+service SecurityService {
+ // ListRoles lists all the roles based on optional filter.
+ rpc ListRoles(ListRolesRequest) returns (ListRolesResponse) {}
+
+ rpc CreateRole(CreateRoleRequest) returns (CreateRoleResponse) {}
+
+ // GetRole retrieves the specific role.
+ rpc GetRole(GetRoleRequest) returns (GetRoleResponse) {}
+
+ // DeleteRole deletes the role from the system.
+ rpc DeleteRole(DeleteRoleRequest) returns (DeleteRoleResponse) {}
+
+ // ListRoleMembership lists all the members assigned to a role based on optional filter.
+ rpc ListRoleMembers(ListRoleMembersRequest) returns (ListRoleMembersResponse) {}
+
+ // UpdateRoleMembership updates role membership.
+ // Partially update role membership, adding or removing from a role
+ // ONLY those members listed in the “add” or “remove” fields, respectively.
+ // Adding a member that is already assigned to the role (or removing one that is not) is a no-op,
+ // and the rest of the members will be added and removed and reported.
+ rpc UpdateRoleMembership(UpdateRoleMembershipRequest) returns (UpdateRoleMembershipResponse) {}
+}