Skip to content

Commit

Permalink
frontend: wip list and details pages
Browse files Browse the repository at this point in the history
  • Loading branch information
rikimaru0345 committed May 16, 2024
1 parent 954c419 commit b6ee758
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 0 deletions.
89 changes: 89 additions & 0 deletions frontend/src/components/pages/rp-connect/Pipelines.Details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/**
* 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
*/

/* eslint-disable no-useless-escape */
import Section from '../../misc/Section';
import { makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { appGlobal } from '../../../state/appGlobal';
import { pipelinesApi } from '../../../state/backendApi';
import PageContent from '../../misc/PageContent';
import { PageComponent, PageInitHelper } from '../Page';
import { Link } from 'react-router-dom';
import { Button } from '@redpanda-data/ui';
import { ClusterConnectorInfo } from '../../../state/restInterfaces';
import { uiSettings } from '../../../state/ui';
import { DefaultSkeleton } from '../../../utils/tsxUtils';


@observer
class RpConnectPipelinesDetails extends PageComponent<{ connectorName: string }> {

@observable placeholder = 5;
@observable filteredResults: ClusterConnectorInfo[] = [];

constructor(p: any) {
super(p);
makeObservable(this);
}

initPage(p: PageInitHelper): void {
const connectorName = decodeURIComponent(this.props.connectorName);
p.title = connectorName;
p.addBreadcrumb('Redpanda Connect', '/rp-connect');
p.addBreadcrumb(connectorName, `/rp-connect/${connectorName}`);

this.refreshData(true);
appGlobal.onRefresh = () => this.refreshData(true);
}

refreshData(force: boolean) {
pipelinesApi.refreshPipelines(force);
}

isFilterMatch(filter: string, item: ClusterConnectorInfo): boolean {
try {
const quickSearchRegExp = new RegExp(uiSettings.pipelinesList.quickSearch, 'i')
return Boolean(item.name.match(quickSearchRegExp)) || Boolean(item.class.match(quickSearchRegExp))
} catch (e) {
console.warn('Invalid expression');
return item.name.toLowerCase().includes(filter.toLowerCase());
}
}

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

return (
<PageContent>

<Section>

<div>
<div style={{ display: 'flex', marginBottom: '.5em' }}>
<Link to={'/connect-clusters/create-connector'}><Button variant="solid" colorScheme="brand" isDisabled>Create connector</Button></Link>
</div>

{/* deploy button */}

{/* yaml editor */}


</div>
</Section>
</PageContent>
);
}
}

export default RpConnectPipelinesDetails;


125 changes: 125 additions & 0 deletions frontend/src/components/pages/rp-connect/Pipelines.List.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
/**
* 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
*/

/* eslint-disable no-useless-escape */
import Section from '../../misc/Section';
import { makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import { appGlobal } from '../../../state/appGlobal';
import { pipelinesApi } from '../../../state/backendApi';
import PageContent from '../../misc/PageContent';
import { PageComponent, PageInitHelper } from '../Page';
import { Link } from 'react-router-dom';
import { Box, Button, DataTable, SearchField, Text } from '@redpanda-data/ui';
import { uiSettings } from '../../../state/ui';
import { DefaultSkeleton } from '../../../utils/tsxUtils';
import { ConnectPipeline, ConnectPipeline_State } from '../../../protogen/redpanda/api/console/v1alpha1/rp_connect_pb';
import { proto3 } from '@bufbuild/protobuf';

@observer
class RpConnectPipelinesList extends PageComponent<{}> {

@observable placeholder = 5;

constructor(p: any) {
super(p);
makeObservable(this);
}

initPage(p: PageInitHelper): void {
p.addBreadcrumb('Redpanda Connect Pipelines', '/rp-connect');

this.refreshData(true);
appGlobal.onRefresh = () => this.refreshData(true);
}

refreshData(force: boolean) {
pipelinesApi.refreshPipelines(force);
}

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

const filteredPipelines = (pipelinesApi.pipelines ?? [])
.filter(u => {
const filter = uiSettings.pipelinesList.quickSearch;
if (!filter) return true;
try {
const quickSearchRegExp = new RegExp(filter, 'i');
return u.name.match(quickSearchRegExp);
} catch { return false; }
});

return (
<PageContent>
<Section>
{/* Connectors List */}
<div>
<div style={{ display: 'flex', marginBottom: '.5em' }}>
<Link to={'/rp-connect/create-connector'}><Button variant="solid" colorScheme="brand" isDisabled>Create connector</Button></Link>
</div>

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

<DataTable<ConnectPipeline>
data={filteredPipelines}
pagination
defaultPageSize={10}
sorting
columns={[
{
header: 'Pipeline Name',
cell: ({ row: { original } }) => (
<Link to={`/rp-connect/${encodeURIComponent(original.name)}`}>
<Text wordBreak="break-word" whiteSpace="break-spaces">{original.name}</Text>
</Link>
),
size: Infinity
},
{
header: 'State',
cell: ({ row: { original } }) => {
const enumType = proto3.getEnumType(ConnectPipeline_State);
const entry = enumType.findNumber(original.state);
return <>
{entry?.name ?? original.state}
</>
}
},
{
header: 'Input',
accessorKey: 'input',
size: 100,
},
{
header: 'Output',
accessorKey: 'output',
size: 100,
}
]}
/>
</div>

</Section>
</PageContent>
);
}
}

export default RpConnectPipelinesList;


5 changes: 5 additions & 0 deletions frontend/src/components/routes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,8 @@ import UserEditPage from './pages/acls/UserEdit';
import RoleCreatePage from './pages/acls/RoleCreate';
import RoleDetailsPage from './pages/acls/RoleDetails';
import RoleEditPage from './pages/acls/RoleEditPage';
import RpConnectPipelinesList from './pages/rp-connect/Pipelines.List';
import RpConnectPipelinesDetails from './pages/rp-connect/Pipelines.Details';

//
// Route Types
Expand Down Expand Up @@ -276,6 +278,9 @@ export const APP_ROUTES: IRouteEntry[] = [
MakeRoute<{ clusterName: string}>('/connect-clusters/:clusterName/create-connector', CreateConnector, 'Create Connector', undefined, undefined, routeVisibility(false)),
MakeRoute<{ clusterName: string, connector: string }>('/connect-clusters/:clusterName/:connector', KafkaConnectorDetails, 'Connector Details'),

MakeRoute<{}>('/rp-connect', RpConnectPipelinesList, 'Redpanda Connect Pipelines', LinkIcon, true),
MakeRoute<{ connectorName: string }>('/rp-connect/:connectorName', RpConnectPipelinesDetails, 'Redpanda Connect Pipelines'),

MakeRoute<{}>('/reassign-partitions', ReassignPartitions, 'Reassign Partitions', BeakerIcon, false,
routeVisibility(true,
[Feature.GetReassignments, Feature.PatchReassignments],
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { Interceptor as ConnectRpcInterceptor, StreamRequest, UnaryRequest, crea
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';
import { RedpandaConnectService } from './protogen/redpanda/api/console/v1alpha1/rp_connect_connect';

declare const __webpack_public_path__: string;

Expand Down Expand Up @@ -65,6 +66,7 @@ interface Config {
restBasePath: string;
consoleClient?: PromiseClient<typeof ConsoleService>;
securityClient?: PromiseClient<typeof SecurityService>;
pipelinesClient?: PromiseClient<typeof RedpandaConnectService>;
fetch: WindowOrWorkerGlobalScope['fetch'];
assetsPath: string;
jwt?: string;
Expand Down Expand Up @@ -98,6 +100,7 @@ const setConfig = ({ fetch, urlOverride, jwt, isServerless, ...args }: SetConfig

const consoleGrpcClient = createPromiseClient(ConsoleService, transport);
const securityGrpcClient = createPromiseClient(SecurityService, transport);
const pipelinesGrpcClient = createPromiseClient(RedpandaConnectService, transport);
Object.assign(config, {
jwt,
isServerless,
Expand All @@ -106,6 +109,7 @@ const setConfig = ({ fetch, urlOverride, jwt, isServerless, ...args }: SetConfig
assetsPath: assetsUrl ?? getBasePath(),
consoleClient: consoleGrpcClient,
securityClient: securityGrpcClient,
pipelinesClient: pipelinesGrpcClient,
...args,
});
return config;
Expand Down
37 changes: 37 additions & 0 deletions frontend/src/state/backendApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ import { CompressionType as ProtoCompressionType, PayloadEncoding } from '../pro
import { PublishMessageRequest, PublishMessageResponse } from '../protogen/redpanda/api/console/v1alpha1/publish_messages_pb';
import { PartitionOffsetOrigin } from './ui';
import { Features } from './supportedFeatures';
import { ConnectPipeline } from '../protogen/redpanda/api/console/v1alpha1/rp_connect_pb';

const REST_TIMEOUT_SEC = 25;
export const REST_CACHE_DURATION_SEC = 20;
Expand Down Expand Up @@ -1640,6 +1641,42 @@ export const rolesApi = observable({
},
});

export const pipelinesApi = observable({
pipelines: undefined as undefined | ConnectPipeline[],

async refreshPipelines(_force: boolean): Promise<void> {

// todo: caching by default, if force=true, ignore time limit on cache

const client = appConfig.pipelinesClient;
if (!client) throw new Error('pipelines client is not initialized');

const pipelines = [];

let nextPageToken = '';
while (true) {
const res = await client.listConnectPipelines({ pageSize: 500, pageToken: nextPageToken });

pipelines.push(...res.pipelines);

if (!res.nextPageToken || res.nextPageToken.length == 0)
break;
nextPageToken = res.nextPageToken;
}

this.pipelines = pipelines;
},

// async refreshPipelineDetails(name: string): Promise<void> {
// const client = appConfig.pipelinesClient;
// if (!client) throw new Error('pipelines client is not initialized');
//
// const pipelineDetails = await client.getConnectPipeline({ name });
// pipelineDetails.pipeline?.config
// },

});


export function createMessageSearch() {
const messageSearch = {
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/state/ui.ts
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,10 @@ const defaultUiSettings = {
sorting: [] as SortingState,
},

pipelinesList: {
quickSearch: ''
},

consumerGroupList: {
pageSize: DEFAULT_TABLE_PAGE_SIZE,
quickSearch: '',
Expand Down

0 comments on commit b6ee758

Please sign in to comment.