Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add redpanda connect secret manager to console #1527

Merged
merged 19 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
841b35a
console: add rpcn create secret page
andresaristizabal Nov 21, 2024
7cabae5
console: add rpcn list secret page
andresaristizabal Nov 21, 2024
bf4f64f
console: add rpcn update secret page
andresaristizabal Nov 21, 2024
dcc1289
console: add redpanda connect grpc client
andresaristizabal Nov 21, 2024
2cabe88
console: add redpanda connect secret routes
andresaristizabal Nov 21, 2024
d87484c
console: use same design buttons as last secret manager design
andresaristizabal Nov 21, 2024
6bbb7c3
console: add secret tab in redpanda connect page
andresaristizabal Nov 21, 2024
390fddf
console: add autocomplete secrets to create pipeline
andresaristizabal Nov 21, 2024
b8b41cc
console: add autocomplete secrets to edit pipeline
andresaristizabal Nov 22, 2024
8e90601
console: add query parameter to select the default Connect tab to open.
andresaristizabal Nov 22, 2024
837d6e5
rp-connect: hide search field if there are no secret or pipeline
andresaristizabal Nov 24, 2024
deaac9a
rp-connect: add password input for secret values.
andresaristizabal Nov 24, 2024
94e4bc6
connect: add copy button to secret list
andresaristizabal Nov 25, 2024
d03be2e
pipeline-editor: disable secret autocomplete
andresaristizabal Nov 26, 2024
b329bba
rpcn-secret: fix typo
andresaristizabal Nov 26, 2024
b7f33ca
rpcn-secret: use util base64 encode fn
andresaristizabal Nov 27, 2024
5421d5a
rpcn-secret: use correct style for button
andresaristizabal Nov 27, 2024
05837ee
rpcn-secret: change secrets on url instead of secret
andresaristizabal Nov 27, 2024
a30244f
rpcn-secret: add manual validation for secret name
andresaristizabal Nov 28, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 68 additions & 15 deletions frontend/src/components/pages/connect/Overview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,14 @@
*/

import { observer, useLocalObservable } from 'mobx-react';
import { Component } from 'react';
import {Component, FunctionComponent} from 'react';
import { appGlobal } from '../../../state/appGlobal';
import { api } from '../../../state/backendApi';
import { ClusterConnectorInfo, ClusterConnectors, ClusterConnectorTaskInfo } from '../../../state/restInterfaces';
import { uiSettings } from '../../../state/ui';
import { Code, DefaultSkeleton } from '../../../utils/tsxUtils';
import Tabs, { Tab } from '../../misc/tabs/Tabs';
import { PageComponent, PageInitHelper } from '../Page';
import {PageComponent, PageInitHelper} from '../Page';
import { ConnectorClass, ConnectorsColumn, errIcon, mr05, NotConfigured, OverviewStatisticsCard, TasksColumn, TaskState } from './helper';
import Section from '../../misc/Section';
import PageContent from '../../misc/PageContent';
Expand All @@ -27,9 +27,50 @@ import RpConnectPipelinesList from '../rp-connect/Pipelines.List';
import { RedpandaConnectIntro } from '../rp-connect/RedpandaConnectIntro';
import { Features } from '../../../state/supportedFeatures';
import {isServerless} from '../../../config';
import RpConnectSecretsList from '../rp-connect/secrets/Secrets.List';
import { useLocation } from 'react-router-dom';

enum ConnectView {
KafkaConnect = 'kafka-connect',
RedpandaConnect = 'redpanda-connect',
RedpandaConnectSecret = 'redpanda-connect-secret',
}

/**
* The Redpanda Connect Secret Manager introduces a new tab in Redpanda Connect.
* this logic determines which tab should be opened based on the `defaultTab`
* query parameter in the URL.
*/
const getDefaultView = (defaultView: string): { initialTab: ConnectView, redpandaConnectTab: ConnectView } => {

const showPipelines = Features.pipelinesApi
const showKafkaTab = {initialTab: ConnectView.KafkaConnect, redpandaConnectTab: ConnectView.RedpandaConnect}
const showRedpandaConnectTab = {initialTab: ConnectView.RedpandaConnect, redpandaConnectTab: ConnectView.RedpandaConnect}
if (!showPipelines) {
return showKafkaTab;
}

switch (defaultView) {
case 'kafka-connect':
return showKafkaTab;
case 'redpanda-connect':
return showRedpandaConnectTab;
case 'redpanda-connect-secret':
return {initialTab: ConnectView.RedpandaConnect, redpandaConnectTab: ConnectView.RedpandaConnectSecret};
default:
return showRedpandaConnectTab;
}
}

const WrapUseSearchParamsHook: FunctionComponent<{matchedPath: string}> = (props) => {
const {search}= useLocation();
const searchParams = new URLSearchParams(search);
const defaultTab = searchParams.get('defaultTab') || '';
return <KafkaConnectOverview defaultView={defaultTab} {...props}/>
Copy link
Contributor

@malinskibeniamin malinskibeniamin Nov 26, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if defaultTab is ''? I think we need to return to tabConnectView.RedpandaConnect by default?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should default from getDefaultView()

}

@observer
class KafkaConnectOverview extends PageComponent {
class KafkaConnectOverview extends PageComponent<{ defaultView: string }> {
initPage(p: PageInitHelper): void {
p.title = 'Overview';
p.addBreadcrumb('Connect', '/connect-clusters');
Expand All @@ -50,22 +91,20 @@ class KafkaConnectOverview extends PageComponent {
}

render() {
const showPipelines = Features.pipelinesApi

const tabs = [
{
key: 'redpandaConnect',
key: ConnectView.RedpandaConnect,
title: <Box minWidth="180px">Redpanda Connect <Badge ml={2}>Recommended</Badge></Box>,
content:
<Box>
<Text mb={4}>
Redpanda Connect is an alternative to Kafka Connect. Choose from a growing ecosystem of readily available connectors. <Link href="https://docs.redpanda.com/redpanda-cloud/develop/connect/about/" target="_blank">Learn more.</Link>
</Text>
<TabRedpandaConnect />
<TabRedpandaConnect defaultView={getDefaultView(this.props.defaultView).redpandaConnectTab}/>
</Box>,
},
{
key: 'kafkaConnect',
key: ConnectView.KafkaConnect,
title: <Box minWidth="180px">Kafka Connect</Box>,
content:
<Box>
Expand All @@ -77,7 +116,7 @@ class KafkaConnectOverview extends PageComponent {
] as Tab[];

if (isServerless())
tabs.removeAll(x => x.key == 'kafkaConnect');
tabs.removeAll(x => x.key == ConnectView.KafkaConnect);

return (
<PageContent>
Expand All @@ -89,14 +128,14 @@ class KafkaConnectOverview extends PageComponent {
? tabs[0].content()
: tabs[0].content
)
: <Tabs tabs={tabs} defaultSelectedTabKey={showPipelines ? 'redpandaConnect' : 'kafkaConnect'} />
: <Tabs tabs={tabs} defaultSelectedTabKey={getDefaultView(this.props.defaultView).initialTab} />
jvorcak marked this conversation as resolved.
Show resolved Hide resolved
}
</PageContent>
);
}
}

export default KafkaConnectOverview;
export default WrapUseSearchParamsHook;

@observer
class TabClusters extends Component {
Expand Down Expand Up @@ -326,11 +365,25 @@ const TabKafkaConnect = observer((_p: {}) => {
})


const TabRedpandaConnect = observer((_p: {}) => {
const TabRedpandaConnect = observer((_p: {defaultView: ConnectView}) => {
if (!Features.pipelinesApi) // If the backend doesn't support pipelines, show the intro page
return <RedpandaConnectIntro />

return <RpConnectPipelinesList matchedPath="/rp-connect" />
return <RedpandaConnectIntro/>

const tabs = [
{
key: 'pipelines',
title: <Box minWidth="180px">Pipelines</Box>,
content: <RpConnectPipelinesList matchedPath="/rp-connect"/>,
},
{
key: 'secrets',
title: <Box minWidth="180px">Secrets</Box>,
content:
<RpConnectSecretsList matchedPath="/rp-connect/secrets"/>
},
] as Tab[];

return <Tabs tabs={tabs} defaultSelectedTabKey={_p.defaultView === ConnectView.RedpandaConnectSecret ? 'secrets' : 'pipelines'}/>
})

export type ConnectTabKeys = 'clusters' | 'connectors' | 'tasks';
Expand Down
15 changes: 10 additions & 5 deletions frontend/src/components/pages/rp-connect/Pipelines.Create.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import PageContent from '../../misc/PageContent';
import { PageComponent, PageInitHelper } from '../Page';
import { Alert, AlertIcon, Box, Button, Text, createStandaloneToast, Flex, FormField, Input } from '@redpanda-data/ui';
import PipelinesYamlEditor from '../../misc/PipelinesYamlEditor';
import { pipelinesApi } from '../../../state/backendApi';
import {pipelinesApi, rpcnSecretManagerApi} from '../../../state/backendApi';
import { DefaultSkeleton } from '../../../utils/tsxUtils';
import { Link } from 'react-router-dom';
import { Link as ChLink } from '@redpanda-data/ui';
Expand All @@ -36,7 +36,7 @@ class RpConnectPipelinesCreate extends PageComponent<{}> {
@observable description = '';
@observable editorContent = exampleContent;
@observable isCreating = false;

@observable secrets: string[] = []
constructor(p: any) {
super(p);
makeObservable(this, undefined, { autoBind: true });
Expand All @@ -48,6 +48,8 @@ class RpConnectPipelinesCreate extends PageComponent<{}> {
p.addBreadcrumb('Create Pipeline', '');

this.refreshData(true);
// get secrets
rpcnSecretManagerApi.refreshSecrets(true);
appGlobal.onRefresh = () => this.refreshData(true);
}

Expand All @@ -58,7 +60,10 @@ class RpConnectPipelinesCreate extends PageComponent<{}> {

render() {
if (!pipelinesApi.pipelines) return DefaultSkeleton;

if (rpcnSecretManagerApi.secrets) {
// inject secrets to editor
this.secrets.updateWith(rpcnSecretManagerApi.secrets.map(value => value.id))
}
const alreadyExists = pipelinesApi.pipelines.any(x => x.id == this.fileName);
const isNameEmpty = this.fileName.trim().length == 0;

Expand Down Expand Up @@ -99,7 +104,7 @@ class RpConnectPipelinesCreate extends PageComponent<{}> {
</Flex>

<Box mt="4">
<PipelineEditor yaml={this.editorContent} onChange={x => this.editorContent = x} />
<PipelineEditor yaml={this.editorContent} onChange={x => this.editorContent = x} secrets={this.secrets}/>
</Box>

<Flex alignItems="center" gap="4">
Expand Down Expand Up @@ -156,8 +161,8 @@ export default RpConnectPipelinesCreate;
export const PipelineEditor = observer((p: {
yaml: string,
onChange: (newYaml: string) => void
secrets?: string[]
}) => {

return <Tabs tabs={[
{
key: 'config', title: 'Configuration',
Expand Down
12 changes: 9 additions & 3 deletions frontend/src/components/pages/rp-connect/Pipelines.Edit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import { appGlobal } from '../../../state/appGlobal';
import PageContent from '../../misc/PageContent';
import { PageComponent, PageInitHelper } from '../Page';
import { Box, Button, createStandaloneToast, Flex, FormField, Input } from '@redpanda-data/ui';
import { pipelinesApi } from '../../../state/backendApi';
import {pipelinesApi, rpcnSecretManagerApi} from '../../../state/backendApi';
import { DefaultSkeleton } from '../../../utils/tsxUtils';
import { Link } from 'react-router-dom';
import { PipelineUpdate } from '../../../protogen/redpanda/api/dataplane/v1alpha2/pipeline_pb';
Expand All @@ -33,6 +33,7 @@ class RpConnectPipelinesEdit extends PageComponent<{ pipelineId: string }> {
@observable description = undefined as unknown as string;
@observable editorContent = undefined as unknown as string;
@observable isUpdating = false;
@observable secrets: string[] = []

constructor(p: any) {
super(p);
Expand All @@ -47,6 +48,8 @@ class RpConnectPipelinesEdit extends PageComponent<{ pipelineId: string }> {
p.addBreadcrumb('Edit Pipeline', `/rp-connect/${pipelineId}/edit`);

this.refreshData(true);
// get secrets
rpcnSecretManagerApi.refreshSecrets(true);
appGlobal.onRefresh = () => this.refreshData(true);
}

Expand All @@ -57,7 +60,10 @@ class RpConnectPipelinesEdit extends PageComponent<{ pipelineId: string }> {

render() {
if (!pipelinesApi.pipelines) return DefaultSkeleton;

if (rpcnSecretManagerApi.secrets) {
// inject secrets to editor
this.secrets.updateWith(rpcnSecretManagerApi.secrets.map(value => value.id))
}
const pipelineId = this.props.pipelineId;
const pipeline = pipelinesApi.pipelines.first(x => x.id == pipelineId);
if (!pipeline) return DefaultSkeleton;
Expand Down Expand Up @@ -104,7 +110,7 @@ class RpConnectPipelinesEdit extends PageComponent<{ pipelineId: string }> {
</FormField>

<Box mt="4">
<PipelineEditor yaml={this.editorContent} onChange={x => this.editorContent = x} />
<PipelineEditor yaml={this.editorContent} onChange={x => this.editorContent = x} secrets={this.secrets} />
</Box>

<Flex alignItems="center" gap="4">
Expand Down
44 changes: 24 additions & 20 deletions frontend/src/components/pages/rp-connect/Pipelines.List.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,19 @@ import { HiX } from 'react-icons/hi';

const { ToastContainer, toast } = createStandaloneToast();

const CreatePipelineButton = () => {
return (<Box style={{ display: 'flex', marginBottom: '.5em' }}>
<Link to={'/rp-connect/create'}><Button>Create pipeline</Button></Link>
</Box>)
}

const EmptyPlaceholder = () => {
return <Flex alignItems="center" justifyContent="center" flexDirection="column" gap="4" mb="4">
<Image src={EmptyConnectors} />
<Box>You have no Redpanda Connect pipelines.</Box>
<CreatePipelineButton/>
</Flex>
};

export const PipelineStatus = observer((p: { status: Pipeline_State }) => {
switch (p.status) {
Expand Down Expand Up @@ -121,17 +134,18 @@ class RpConnectPipelinesList extends PageComponent<{}> {
<ToastContainer />
{/* Pipeline List */}

<div style={{ display: 'flex', marginBottom: '.5em' }}>
<Link to={'/rp-connect/create'}><Button variant="solid" colorScheme="brand">Create pipeline</Button></Link>
</div>
{pipelinesApi.pipelines.length != 0 && (
<Flex my={5} flexDir={'column'} gap={2}>
<CreatePipelineButton/>
<SearchField width="350px"
searchText={uiSettings.pipelinesList.quickSearch}
setSearchText={x => uiSettings.pipelinesList.quickSearch = x}
placeholderText="Enter search term / regex..."
/>
</Flex>
)}


<Box my={5}>
<SearchField width="350px"
searchText={uiSettings.pipelinesList.quickSearch}
setSearchText={x => uiSettings.pipelinesList.quickSearch = x}
placeholderText="Enter search term / regex..."
/>
</Box>

{(pipelinesApi.pipelines ?? []).length == 0
? <EmptyPlaceholder />
Expand Down Expand Up @@ -226,13 +240,3 @@ class RpConnectPipelinesList extends PageComponent<{}> {
}

export default RpConnectPipelinesList;

const EmptyPlaceholder = () => {
return <Flex alignItems="center" justifyContent="center" flexDirection="column" gap="4" mb="4">
<Image src={EmptyConnectors} />
<Box>You have no Redpanda Connect pipelines.</Box>
<Link to="/rp-connect/create">
<Button variant="solid">Create pipeline</Button>
</Link>
</Flex>
};
Loading