Schema registry. Index page
This commit is contained in:
parent
40d85643bb
commit
00613bd4fc
17 changed files with 245 additions and 4 deletions
|
@ -3,10 +3,11 @@ import { Switch, Route, Redirect } from 'react-router-dom';
|
|||
import './App.scss';
|
||||
import BrokersContainer from './Brokers/BrokersContainer';
|
||||
import TopicsContainer from './Topics/TopicsContainer';
|
||||
import NavConatiner from './Nav/NavConatiner';
|
||||
import NavContainer from './Nav/NavContainer';
|
||||
import PageLoader from './common/PageLoader/PageLoader';
|
||||
import Dashboard from './Dashboard/Dashboard';
|
||||
import ConsumersGroupsContainer from './ConsumerGroups/ConsumersGroupsContainer';
|
||||
import SchemasContainer from './Schemas/SchemasContainer';
|
||||
|
||||
interface AppProps {
|
||||
isClusterListFetched: boolean;
|
||||
|
@ -35,7 +36,7 @@ const App: React.FC<AppProps> = ({
|
|||
</div>
|
||||
</nav>
|
||||
<main className="Layout__container">
|
||||
<NavConatiner className="Layout__navbar" />
|
||||
<NavContainer className="Layout__navbar" />
|
||||
{isClusterListFetched ? (
|
||||
<Switch>
|
||||
<Route
|
||||
|
@ -55,6 +56,10 @@ const App: React.FC<AppProps> = ({
|
|||
path="/ui/clusters/:clusterName/consumer-groups"
|
||||
component={ConsumersGroupsContainer}
|
||||
/>
|
||||
<Route
|
||||
path="/ui/clusters/:clusterName/schemas"
|
||||
component={SchemasContainer}
|
||||
/>
|
||||
<Redirect
|
||||
from="/ui/clusters/:clusterName"
|
||||
to="/ui/clusters/:clusterName/brokers"
|
||||
|
|
|
@ -4,6 +4,7 @@ import {
|
|||
clusterBrokersPath,
|
||||
clusterTopicsPath,
|
||||
clusterConsumerGroupsPath,
|
||||
clusterSchemasPath,
|
||||
} from 'lib/paths';
|
||||
import { Cluster, ServerStatus } from 'generated-sources';
|
||||
|
||||
|
@ -85,6 +86,13 @@ const ClusterMenu: React.FC<Props> = ({ cluster }) => (
|
|||
>
|
||||
Consumers
|
||||
</NavLink>
|
||||
<NavLink
|
||||
to={clusterSchemasPath(cluster.name)}
|
||||
activeClassName="is-active"
|
||||
title="Schema Registry"
|
||||
>
|
||||
Schema Registry
|
||||
</NavLink>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
|
|
32
kafka-ui-react-app/src/components/Schemas/List/List.tsx
Normal file
32
kafka-ui-react-app/src/components/Schemas/List/List.tsx
Normal file
|
@ -0,0 +1,32 @@
|
|||
import React from 'react';
|
||||
import { Schema } from 'redux/interfaces';
|
||||
import Breadcrumb from '../../common/Breadcrumb/Breadcrumb';
|
||||
import ListItem from './ListItem';
|
||||
|
||||
interface ListProps {
|
||||
schemas: Schema[];
|
||||
}
|
||||
|
||||
const List: React.FC<ListProps> = ({ schemas }) => {
|
||||
return (
|
||||
<div className="section">
|
||||
<Breadcrumb>Schema Registry</Breadcrumb>
|
||||
<div className="box">
|
||||
<table className="table is-striped is-fullwidth">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Schema Name</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{schemas.map(({ name }) => (
|
||||
<ListItem schemaName={name} />
|
||||
))}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default List;
|
|
@ -0,0 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from 'redux/interfaces';
|
||||
import { withRouter } from 'react-router-dom';
|
||||
import { getSchemaList } from 'redux/reducers/schemas/selectors';
|
||||
import List from './List';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
schemas: getSchemaList(state),
|
||||
});
|
||||
|
||||
export default withRouter(connect(mapStateToProps)(List));
|
15
kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx
Normal file
15
kafka-ui-react-app/src/components/Schemas/List/ListItem.tsx
Normal file
|
@ -0,0 +1,15 @@
|
|||
import React from 'react';
|
||||
|
||||
interface ListItemProps {
|
||||
schemaName: string;
|
||||
}
|
||||
|
||||
const ListItem: React.FC<ListItemProps> = ({ schemaName }) => {
|
||||
return (
|
||||
<tr>
|
||||
<td>{schemaName}</td>
|
||||
</tr>
|
||||
);
|
||||
};
|
||||
|
||||
export default ListItem;
|
41
kafka-ui-react-app/src/components/Schemas/Schemas.tsx
Normal file
41
kafka-ui-react-app/src/components/Schemas/Schemas.tsx
Normal file
|
@ -0,0 +1,41 @@
|
|||
import React from 'react';
|
||||
import { ClusterName } from 'redux/interfaces';
|
||||
import { Switch, Route, useParams } from 'react-router-dom';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import ListContainer from './List/ListContainer';
|
||||
|
||||
interface SchemasProps {
|
||||
isFetched: boolean;
|
||||
fetchSchemasByClusterName: (clusterName: ClusterName) => void;
|
||||
}
|
||||
|
||||
interface ParamTypes {
|
||||
clusterName: string;
|
||||
}
|
||||
|
||||
const Schemas: React.FC<SchemasProps> = ({
|
||||
isFetched,
|
||||
fetchSchemasByClusterName,
|
||||
}) => {
|
||||
const { clusterName } = useParams<ParamTypes>();
|
||||
|
||||
React.useEffect(() => {
|
||||
fetchSchemasByClusterName(clusterName);
|
||||
}, [fetchSchemasByClusterName, clusterName]);
|
||||
|
||||
if (isFetched) {
|
||||
return (
|
||||
<Switch>
|
||||
<Route
|
||||
exact
|
||||
path="/ui/clusters/:clusterName/schemas"
|
||||
component={ListContainer}
|
||||
/>
|
||||
</Switch>
|
||||
);
|
||||
}
|
||||
|
||||
return <PageLoader />;
|
||||
};
|
||||
|
||||
export default Schemas;
|
|
@ -0,0 +1,15 @@
|
|||
import { connect } from 'react-redux';
|
||||
import { RootState } from '../../redux/interfaces';
|
||||
import { fetchSchemasByClusterName } from '../../redux/actions';
|
||||
import Schemas from './Schemas';
|
||||
import { getIsSchemaListFetched } from '../../redux/reducers/schemas/selectors';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isFetched: getIsSchemaListFetched(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchSchemasByClusterName,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Schemas);
|
|
@ -10,6 +10,8 @@ export const clusterTopicNewPath = (clusterName: ClusterName) =>
|
|||
`${clusterPath(clusterName)}/topics/new`;
|
||||
export const clusterConsumerGroupsPath = (clusterName: ClusterName) =>
|
||||
`${clusterPath(clusterName)}/consumer-groups`;
|
||||
export const clusterSchemasPath = (clusterName: ClusterName) =>
|
||||
`${clusterPath(clusterName)}/schemas`;
|
||||
|
||||
export const clusterTopicPath = (
|
||||
clusterName: ClusterName,
|
||||
|
|
|
@ -50,6 +50,10 @@ enum ActionType {
|
|||
GET_CONSUMER_GROUP_DETAILS__REQUEST = 'GET_CONSUMER_GROUP_DETAILS__REQUEST',
|
||||
GET_CONSUMER_GROUP_DETAILS__SUCCESS = 'GET_CONSUMER_GROUP_DETAILS__SUCCESS',
|
||||
GET_CONSUMER_GROUP_DETAILS__FAILURE = 'GET_CONSUMER_GROUP_DETAILS__FAILURE',
|
||||
|
||||
GET_CLUSTER_SCHEMAS__REQUEST = 'GET_CLUSTER_SCHEMAS__REQUEST',
|
||||
GET_CLUSTER_SCHEMAS__SUCCESS = 'GET_CLUSTER_SCHEMAS__SUCCESS',
|
||||
GET_CLUSTER_SCHEMAS__FAILURE = 'GET_CLUSTER_SCHEMAS__FAILURE',
|
||||
}
|
||||
|
||||
export default ActionType;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { createAsyncAction } from 'typesafe-actions';
|
||||
import ActionType from 'redux/actionType';
|
||||
import { TopicName, ConsumerGroupID } from 'redux/interfaces';
|
||||
import { TopicName, ConsumerGroupID, Schema } from 'redux/interfaces';
|
||||
|
||||
import {
|
||||
Cluster,
|
||||
|
@ -97,3 +97,9 @@ export const fetchConsumerGroupDetailsAction = createAsyncAction(
|
|||
{ consumerGroupID: ConsumerGroupID; details: ConsumerGroupDetails },
|
||||
undefined
|
||||
>();
|
||||
|
||||
export const fetchSchemasByClusterNameAction = createAsyncAction(
|
||||
ActionType.GET_CLUSTER_SCHEMAS__REQUEST,
|
||||
ActionType.GET_CLUSTER_SCHEMAS__SUCCESS,
|
||||
ActionType.GET_CLUSTER_SCHEMAS__FAILURE
|
||||
)<undefined, Schema[], undefined>();
|
||||
|
|
|
@ -15,13 +15,14 @@ import {
|
|||
TopicMessageQueryParams,
|
||||
TopicFormFormattedParams,
|
||||
TopicFormDataRaw,
|
||||
Schema,
|
||||
} from 'redux/interfaces';
|
||||
|
||||
import { BASE_PARAMS } from 'lib/constants';
|
||||
import * as actions from './actions';
|
||||
|
||||
const apiClientConf = new Configuration(BASE_PARAMS);
|
||||
const apiClient = new ApiClustersApi(apiClientConf);
|
||||
export const apiClient = new ApiClustersApi(apiClientConf);
|
||||
|
||||
export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(actions.fetchClusterListAction.request());
|
||||
|
@ -250,3 +251,17 @@ export const fetchConsumerGroupDetails = (
|
|||
dispatch(actions.fetchConsumerGroupDetailsAction.failure());
|
||||
}
|
||||
};
|
||||
|
||||
export const fetchSchemasByClusterName = (
|
||||
clusterName: ClusterName
|
||||
): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.request());
|
||||
try {
|
||||
const schemaNames = await apiClient.getSchemas({ clusterName });
|
||||
const schemas: Schema[] = schemaNames.map((name) => ({ name }));
|
||||
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.success(schemas));
|
||||
} catch (e) {
|
||||
dispatch(actions.fetchSchemasByClusterNameAction.failure());
|
||||
}
|
||||
};
|
||||
|
|
|
@ -9,11 +9,13 @@ import { ClusterState } from './cluster';
|
|||
import { BrokersState } from './broker';
|
||||
import { LoaderState } from './loader';
|
||||
import { ConsumerGroupsState } from './consumerGroup';
|
||||
import { SchemasState } from './schema';
|
||||
|
||||
export * from './topic';
|
||||
export * from './cluster';
|
||||
export * from './broker';
|
||||
export * from './consumerGroup';
|
||||
export * from './schema';
|
||||
export * from './loader';
|
||||
|
||||
export enum FetchStatus {
|
||||
|
@ -28,6 +30,7 @@ export interface RootState {
|
|||
clusters: ClusterState;
|
||||
brokers: BrokersState;
|
||||
consumerGroups: ConsumerGroupsState;
|
||||
schemas: SchemasState;
|
||||
loader: LoaderState;
|
||||
}
|
||||
|
||||
|
|
10
kafka-ui-react-app/src/redux/interfaces/schema.ts
Normal file
10
kafka-ui-react-app/src/redux/interfaces/schema.ts
Normal file
|
@ -0,0 +1,10 @@
|
|||
import { SchemaSubject } from 'generated-sources';
|
||||
|
||||
export interface Schema extends Partial<SchemaSubject> {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export interface SchemasState {
|
||||
byName: { [name: string]: Schema };
|
||||
allNames: string[];
|
||||
}
|
|
@ -4,6 +4,7 @@ import topics from './topics/reducer';
|
|||
import clusters from './clusters/reducer';
|
||||
import brokers from './brokers/reducer';
|
||||
import consumerGroups from './consumerGroups/reducer';
|
||||
import schemas from './schemas/reducer';
|
||||
import loader from './loader/reducer';
|
||||
|
||||
export default combineReducers<RootState>({
|
||||
|
@ -11,5 +12,6 @@ export default combineReducers<RootState>({
|
|||
clusters,
|
||||
brokers,
|
||||
consumerGroups,
|
||||
schemas,
|
||||
loader,
|
||||
});
|
||||
|
|
43
kafka-ui-react-app/src/redux/reducers/schemas/reducer.ts
Normal file
43
kafka-ui-react-app/src/redux/reducers/schemas/reducer.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import ActionType from 'redux/actionType';
|
||||
import { Action, Schema, SchemasState } from 'redux/interfaces';
|
||||
|
||||
export const initialState: SchemasState = {
|
||||
byName: {},
|
||||
allNames: [],
|
||||
};
|
||||
|
||||
const updateSchemaList = (
|
||||
state: SchemasState,
|
||||
payload: Schema[]
|
||||
): SchemasState => {
|
||||
const initialMemo: SchemasState = {
|
||||
...state,
|
||||
allNames: [],
|
||||
};
|
||||
|
||||
return payload.reduce(
|
||||
(memo: SchemasState, schema) => ({
|
||||
...memo,
|
||||
byName: {
|
||||
...memo.byName,
|
||||
[schema.name]: {
|
||||
...memo.byName[schema.name],
|
||||
name: schema.name,
|
||||
},
|
||||
},
|
||||
allNames: [...memo.allNames, schema.name],
|
||||
}),
|
||||
initialMemo
|
||||
);
|
||||
};
|
||||
|
||||
const reducer = (state = initialState, action: Action): SchemasState => {
|
||||
switch (action.type) {
|
||||
case ActionType.GET_CLUSTER_SCHEMAS__SUCCESS:
|
||||
return updateSchemaList(state, action.payload);
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
29
kafka-ui-react-app/src/redux/reducers/schemas/selectors.ts
Normal file
29
kafka-ui-react-app/src/redux/reducers/schemas/selectors.ts
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { FetchStatus, RootState, SchemasState } from 'redux/interfaces';
|
||||
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
|
||||
|
||||
const schemasState = ({ schemas }: RootState): SchemasState => schemas;
|
||||
|
||||
const getAllNames = (state: RootState) => schemasState(state).allNames;
|
||||
const getSchemaMap = (state: RootState) => schemasState(state).byName;
|
||||
|
||||
const getSchemaListFetchingStatus = createFetchingSelector(
|
||||
'GET_CLUSTER_SCHEMAS'
|
||||
);
|
||||
|
||||
export const getIsSchemaListFetched = createSelector(
|
||||
getSchemaListFetchingStatus,
|
||||
(status) => status === FetchStatus.fetched
|
||||
);
|
||||
|
||||
export const getSchemaList = createSelector(
|
||||
getIsSchemaListFetched,
|
||||
getAllNames,
|
||||
getSchemaMap,
|
||||
(isFetched, allNames, byName) => {
|
||||
if (!isFetched) {
|
||||
return [];
|
||||
}
|
||||
return allNames.map((schemaName) => byName[schemaName]);
|
||||
}
|
||||
);
|
Loading…
Add table
Reference in a new issue