Add Cluster
This commit is contained in:
parent
15397cb459
commit
1468adbc45
26 changed files with 350 additions and 169 deletions
|
@ -1,92 +1,29 @@
|
|||
$navbar-width: 64px;
|
||||
$header-height: 52px;
|
||||
$navbar-width: 250px;
|
||||
|
||||
.Layout {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
background-color: #F7F7F7;
|
||||
|
||||
&__navbar {
|
||||
display: flex;
|
||||
z-index: 4;
|
||||
flex-direction: column;
|
||||
width: $navbar-width;
|
||||
margin-left: 0;
|
||||
background-color: #192d3e;
|
||||
box-shadow: 0 0 0 0 rgba(0,0,0,0.2), 0 0 0 0 rgba(0,0,0,0.12), 0px 2px 7px 0px rgba(0,0,0,0.2);
|
||||
text-align: center;
|
||||
|
||||
&Text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
&--expanded {
|
||||
margin-left: 0;
|
||||
width: 280px;
|
||||
text-align: left;
|
||||
|
||||
.Layout__navbar {
|
||||
&Icon {
|
||||
margin-right: 20px;
|
||||
}
|
||||
|
||||
&Text {
|
||||
display: inline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&__logo {
|
||||
height: 52px;
|
||||
color: #fff;
|
||||
text-align: center;
|
||||
line-height: 52px;
|
||||
&__header {
|
||||
box-shadow: 0 0.46875rem 2.1875rem rgba(4,9,20,0.03),
|
||||
0 0.9375rem 1.40625rem rgba(4,9,20,0.03),
|
||||
0 0.25rem 0.53125rem rgba(4,9,20,0.05),
|
||||
0 0.125rem 0.1875rem rgba(4,9,20,0.03);
|
||||
}
|
||||
|
||||
&__container {
|
||||
flex: 1 1 auto;
|
||||
overflow: auto;
|
||||
z-index: 2;
|
||||
margin-top: $header-height;
|
||||
margin-left: $navbar-width;
|
||||
}
|
||||
|
||||
&__content {
|
||||
margin-top: 52px;
|
||||
}
|
||||
|
||||
&__header {
|
||||
&__navbar {
|
||||
width: $navbar-width;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 7px 0 60px rgba(0,0,0,0.05);
|
||||
position: fixed;
|
||||
top: 0;
|
||||
background-color: #F7F7F7;
|
||||
width: 100%;
|
||||
z-index: 3;
|
||||
transition: box-shadow 300ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
||||
box-shadow: 0 0 0 0 rgba(0,0,0,0.2), 0px 3px 5px 0px rgba(0,0,0,0.1), 0 0 0 0 rgba(0,0,0,0.12);
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1200px) {
|
||||
.Layout__navbar {
|
||||
margin-left: -$navbar-width;
|
||||
|
||||
&--expanded {
|
||||
margin-left: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.Layout__navbar {
|
||||
margin-left: -$navbar-width;
|
||||
|
||||
&--expanded {
|
||||
margin-left: 0;
|
||||
position: fixed;
|
||||
top: 52px;
|
||||
top: $header-height;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
padding: 20px 20px;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,60 +2,47 @@ import React from 'react';
|
|||
import {
|
||||
Switch,
|
||||
Route,
|
||||
NavLink,
|
||||
} from 'react-router-dom';
|
||||
import './App.scss';
|
||||
import TopicsContainer from './Topics/TopicsContainer';
|
||||
import NavConatiner from './Nav/NavConatiner';
|
||||
import PageLoader from './common/PageLoader/PageLoader';
|
||||
|
||||
const App: React.FC = () => {
|
||||
const [expandedNavbar, setExpandedNavbar] = React.useState<boolean>(false);
|
||||
const toggleNavbar = () => setExpandedNavbar(!expandedNavbar);
|
||||
interface AppProps {
|
||||
isClusterListFetched: boolean;
|
||||
fetchClustersList: () => void;
|
||||
}
|
||||
|
||||
const App: React.FC<AppProps> = ({
|
||||
isClusterListFetched,
|
||||
fetchClustersList,
|
||||
}) => {
|
||||
React.useEffect(() => { fetchClustersList() }, [fetchClustersList]);
|
||||
|
||||
return (
|
||||
<div className="Layout">
|
||||
<aside className={`Layout__navbar ${expandedNavbar && 'Layout__navbar--expanded'}`}>
|
||||
<header className="Layout__logo">
|
||||
<nav className="navbar is-fixed-top is-white Layout__header" role="navigation" aria-label="main navigation">
|
||||
<div className="navbar-brand">
|
||||
<a className="navbar-item title is-5 is-marginless" href="/">
|
||||
Kafka UI
|
||||
</header>
|
||||
<div className="menu">
|
||||
<ul className="menu-list">
|
||||
<li>
|
||||
<NavLink exact to="/" activeClassName="is-active">
|
||||
<i className="fas fa-tachometer-alt Layout__navbarIcon"></i>
|
||||
<span className="Layout__navbarText">
|
||||
Dashboard
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
<li>
|
||||
<NavLink to="/topics" activeClassName="is-active">
|
||||
<i className="fas fa-stream Layout__navbarIcon"></i>
|
||||
<span className="Layout__navbarText">
|
||||
Topics
|
||||
</span>
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</aside>
|
||||
<main className="Layout__container">
|
||||
<nav className="Layout__header navbar">
|
||||
<div className="navbar-item">
|
||||
<a title="Collapse" href="#" onClick={toggleNavbar}>
|
||||
<span className="icon">
|
||||
<i className="icon fas fa-bars"></i>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</nav>
|
||||
<div className="Layout__content">
|
||||
<main className="Layout__container">
|
||||
<NavConatiner className="Layout__navbar" />
|
||||
{isClusterListFetched ? (
|
||||
<section className="section">
|
||||
<Switch>
|
||||
<Route path="/topics" component={TopicsContainer} />
|
||||
<Route path="/clusters/:clusterId/topics" component={TopicsContainer} />
|
||||
<Route exact path="/">
|
||||
Dashboard
|
||||
</Route>
|
||||
</Switch>
|
||||
</div>
|
||||
</section>
|
||||
) : (
|
||||
<PageLoader />
|
||||
)}
|
||||
|
||||
</main>
|
||||
</div>
|
||||
);
|
||||
|
|
17
frontend/src/components/AppContainer.tsx
Normal file
17
frontend/src/components/AppContainer.tsx
Normal file
|
@ -0,0 +1,17 @@
|
|||
import { connect } from 'react-redux';
|
||||
import {
|
||||
fetchClustersList,
|
||||
} from 'redux/reducers/clusters/thunks';
|
||||
import App from './App';
|
||||
import { getIsClusterListFetched } from 'redux/reducers/clusters/selectors';
|
||||
import { RootState } from 'types';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isClusterListFetched: getIsClusterListFetched(state),
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchClustersList,
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(App);
|
42
frontend/src/components/Nav/ClusterMenu.tsx
Normal file
42
frontend/src/components/Nav/ClusterMenu.tsx
Normal file
|
@ -0,0 +1,42 @@
|
|||
import React, { CSSProperties } from 'react';
|
||||
import { Cluster } from 'types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
interface Props extends Cluster {}
|
||||
|
||||
const DefaultIcon: React.FC = () => {
|
||||
const style: CSSProperties = {
|
||||
width: '.6rem',
|
||||
left: '-8px',
|
||||
top: '-4px',
|
||||
position: 'relative',
|
||||
};
|
||||
|
||||
return (
|
||||
<span title="Default Cluster" className="icon has-text-primary is-small">
|
||||
<i style={style} data-fa-transform="rotate-340" className="fas fa-thumbtack" />
|
||||
</span>
|
||||
)
|
||||
};
|
||||
|
||||
const ClusterMenu: React.FC<Props> = ({
|
||||
id,
|
||||
name,
|
||||
defaultCluster,
|
||||
}) => (
|
||||
<ul className="menu-list">
|
||||
<li>
|
||||
<NavLink exact to={`/clusters/${id}`} activeClassName="is-active" title={name} className="has-text-overflow-ellipsis">
|
||||
{defaultCluster && <DefaultIcon />}
|
||||
{name}
|
||||
</NavLink>
|
||||
<ul>
|
||||
<NavLink to={`/clusters/${id}/topics`} activeClassName="is-active" title="Dashboard">
|
||||
Topics
|
||||
</NavLink>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
);
|
||||
|
||||
export default ClusterMenu;
|
38
frontend/src/components/Nav/Nav.tsx
Normal file
38
frontend/src/components/Nav/Nav.tsx
Normal file
|
@ -0,0 +1,38 @@
|
|||
import React from 'react';
|
||||
import { Cluster } from 'types';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
import cx from 'classnames';
|
||||
import ClusterMenu from './ClusterMenu';
|
||||
|
||||
interface Props {
|
||||
isClusterListFetched: boolean,
|
||||
clusters: Cluster[];
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const Nav: React.FC<Props> = ({
|
||||
isClusterListFetched,
|
||||
clusters,
|
||||
className,
|
||||
}) => (
|
||||
<aside className={cx('menu has-shadow has-background-white', className)}>
|
||||
<p className="menu-label">
|
||||
General
|
||||
</p>
|
||||
<ul className="menu-list">
|
||||
<li>
|
||||
<NavLink exact to="/" activeClassName="is-active" title="Dashboard">
|
||||
Dashboard
|
||||
</NavLink>
|
||||
</li>
|
||||
</ul>
|
||||
<p className="menu-label">
|
||||
Clusters
|
||||
</p>
|
||||
{!isClusterListFetched && <div className="loader" />}
|
||||
|
||||
{isClusterListFetched && clusters.map((cluster) => <ClusterMenu {...cluster} key={cluster.id}/>)}
|
||||
</aside>
|
||||
);
|
||||
|
||||
export default Nav;
|
11
frontend/src/components/Nav/NavConatiner.ts
Normal file
11
frontend/src/components/Nav/NavConatiner.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { connect } from 'react-redux';
|
||||
import Nav from './Nav';
|
||||
import { getIsClusterListFetched, getClusterList } from 'redux/reducers/clusters/selectors';
|
||||
import { RootState } from 'types';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
isClusterListFetched: getIsClusterListFetched(state),
|
||||
clusters: getClusterList(state),
|
||||
});
|
||||
|
||||
export default connect(mapStateToProps)(Nav);
|
|
@ -2,7 +2,6 @@ import React from 'react';
|
|||
import { Topic } from 'types';
|
||||
import ConfigRow from './ConfigRow';
|
||||
import Partition from './Partition';
|
||||
import { NavLink } from 'react-router-dom';
|
||||
|
||||
const Details: React.FC<{ topic: Topic }> = ({
|
||||
topic: {
|
||||
|
|
|
@ -6,20 +6,23 @@ import {
|
|||
import ListContainer from './List/ListContainer';
|
||||
import DetailsContainer from './Details/DetailsContainer';
|
||||
import PageLoader from 'components/common/PageLoader/PageLoader';
|
||||
import { ClusterId } from 'types';
|
||||
|
||||
interface Props {
|
||||
clusterId: string;
|
||||
isFetched: boolean;
|
||||
fetchBrokers: () => void;
|
||||
fetchTopicList: () => void;
|
||||
fetchBrokers: (clusterId: ClusterId) => void;
|
||||
fetchTopicList: (clusterId: ClusterId) => void;
|
||||
}
|
||||
|
||||
const Topics: React.FC<Props> = ({
|
||||
clusterId,
|
||||
isFetched,
|
||||
fetchBrokers,
|
||||
fetchTopicList,
|
||||
}) => {
|
||||
React.useEffect(() => { fetchTopicList(); }, [fetchTopicList]);
|
||||
React.useEffect(() => { fetchBrokers(); }, [fetchBrokers]);
|
||||
React.useEffect(() => { fetchTopicList(clusterId); }, [fetchTopicList, clusterId]);
|
||||
React.useEffect(() => { fetchBrokers(clusterId); }, [fetchBrokers, clusterId]);
|
||||
|
||||
if (isFetched) {
|
||||
return (
|
||||
|
|
|
@ -5,15 +5,23 @@ import {
|
|||
} from 'redux/reducers/topics/thunks';
|
||||
import Topics from './Topics';
|
||||
import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
|
||||
import { RootState } from 'types';
|
||||
import { RootState, ClusterId } from 'types';
|
||||
import { RouteComponentProps } from 'react-router-dom';
|
||||
|
||||
const mapStateToProps = (state: RootState) => ({
|
||||
interface RouteProps {
|
||||
clusterId: string;
|
||||
}
|
||||
|
||||
interface OwnProps extends RouteComponentProps<RouteProps> { }
|
||||
|
||||
const mapStateToProps = (state: RootState, { match: { params: { clusterId } }}: OwnProps) => ({
|
||||
isFetched: getIsTopicListFetched(state),
|
||||
clusterId,
|
||||
});
|
||||
|
||||
const mapDispatchToProps = {
|
||||
fetchTopicList,
|
||||
fetchBrokers,
|
||||
fetchTopicList: (clusterId: ClusterId) => fetchTopicList(clusterId),
|
||||
fetchBrokers: (clusterId: ClusterId) => fetchBrokers(clusterId),
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(Topics);
|
||||
|
|
|
@ -4,7 +4,7 @@ import ReactDOM from 'react-dom';
|
|||
import { BrowserRouter } from 'react-router-dom';
|
||||
import { Provider } from 'react-redux';
|
||||
import './theme/index.scss';
|
||||
import App from './components/App';
|
||||
import AppContainer from './components/AppContainer';
|
||||
import * as serviceWorker from './serviceWorker';
|
||||
import configureStore from './redux/store/configureStore';
|
||||
|
||||
|
@ -13,7 +13,7 @@ const store = configureStore();
|
|||
ReactDOM.render(
|
||||
<Provider store={store}>
|
||||
<BrowserRouter>
|
||||
<App />
|
||||
<AppContainer />
|
||||
</BrowserRouter>
|
||||
</Provider>,
|
||||
document.getElementById('root'),
|
||||
|
|
11
frontend/src/lib/api/clusters.ts
Normal file
11
frontend/src/lib/api/clusters.ts
Normal file
|
@ -0,0 +1,11 @@
|
|||
import {
|
||||
Cluster,
|
||||
} from 'types';
|
||||
import {
|
||||
BASE_URL,
|
||||
BASE_PARAMS,
|
||||
} from 'lib/constants';
|
||||
|
||||
export const getClusters = (): Promise<Cluster[]> =>
|
||||
fetch(`${BASE_URL}/clusters`, { ...BASE_PARAMS })
|
||||
.then(res => res.json());
|
|
@ -1 +1,2 @@
|
|||
export * from './topics';
|
||||
export * from './clusters';
|
||||
|
|
|
@ -2,27 +2,21 @@ import {
|
|||
TopicName,
|
||||
Topic,
|
||||
Broker,
|
||||
ClusterId,
|
||||
} from 'types';
|
||||
|
||||
const BASE_PARAMS: RequestInit = {
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Accept': 'application/vnd.kafka.v2+json',
|
||||
},
|
||||
};
|
||||
|
||||
const BASE_URL = 'http://localhost:8082';
|
||||
import {
|
||||
BASE_URL,
|
||||
BASE_PARAMS,
|
||||
} from 'lib/constants';
|
||||
|
||||
export const getTopic = (name: TopicName): Promise<Topic> =>
|
||||
fetch(`${BASE_URL}/topics/${name}`, { ...BASE_PARAMS })
|
||||
.then(res => res.json());
|
||||
|
||||
export const getTopics = (): Promise<TopicName[]> =>
|
||||
fetch(`${BASE_URL}/topics`, { ...BASE_PARAMS })
|
||||
export const getTopics = (clusterId: ClusterId): Promise<TopicName[]> =>
|
||||
fetch(`${BASE_URL}/clusters/${clusterId}/topics`, { ...BASE_PARAMS })
|
||||
.then(res => res.json());
|
||||
|
||||
export const getBrokers = (): Promise<{ brokers: Broker[] }> =>
|
||||
fetch(`${BASE_URL}/brokers`, { ...BASE_PARAMS })
|
||||
export const getBrokers = (clusterId: ClusterId): Promise<{ brokers: Broker[] }> =>
|
||||
fetch(`${BASE_URL}/clusters/${clusterId}/brokers`, { ...BASE_PARAMS })
|
||||
.then(res => res.json());
|
||||
|
|
9
frontend/src/lib/constants.ts
Normal file
9
frontend/src/lib/constants.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
export const BASE_PARAMS: RequestInit = {
|
||||
credentials: 'include',
|
||||
mode: 'cors',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
export const BASE_URL = 'http://localhost:3004';
|
|
@ -1,3 +1,7 @@
|
|||
import topicsActionType from './topics/actionType';
|
||||
import clustersActionType from './clusters/actionType';
|
||||
|
||||
export default { ...topicsActionType };
|
||||
export default {
|
||||
...topicsActionType,
|
||||
...clustersActionType,
|
||||
};
|
||||
|
|
7
frontend/src/redux/reducers/clusters/actionType.ts
Normal file
7
frontend/src/redux/reducers/clusters/actionType.ts
Normal file
|
@ -0,0 +1,7 @@
|
|||
enum ActionType {
|
||||
CLUSTERS__FETCH_REQUEST = 'CLUSTERS__FETCH_REQUEST',
|
||||
CLUSTERS__FETCH_SUCCESS = 'CLUSTERS__FETCH_SUCCESS',
|
||||
CLUSTERS__FETCH_FAILURE = 'CLUSTERS__FETCH_FAILURE',
|
||||
}
|
||||
|
||||
export default ActionType;
|
9
frontend/src/redux/reducers/clusters/actions.ts
Normal file
9
frontend/src/redux/reducers/clusters/actions.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { createAsyncAction} from 'typesafe-actions';
|
||||
import ActionType from './actionType';
|
||||
import { Cluster } from 'types';
|
||||
|
||||
export const fetchClusterListAction = createAsyncAction(
|
||||
ActionType.CLUSTERS__FETCH_REQUEST,
|
||||
ActionType.CLUSTERS__FETCH_SUCCESS,
|
||||
ActionType.CLUSTERS__FETCH_FAILURE,
|
||||
)<undefined, Cluster[], undefined>();
|
33
frontend/src/redux/reducers/clusters/reducer.ts
Normal file
33
frontend/src/redux/reducers/clusters/reducer.ts
Normal file
|
@ -0,0 +1,33 @@
|
|||
import { ClustersState, FetchStatus, Action } from 'types';
|
||||
import actionType from 'redux/reducers/actionType';
|
||||
|
||||
export const initialState: ClustersState = {
|
||||
fetchStatus: FetchStatus.notFetched,
|
||||
items: [],
|
||||
};
|
||||
|
||||
const reducer = (state = initialState, action: Action): ClustersState => {
|
||||
switch (action.type) {
|
||||
case actionType.CLUSTERS__FETCH_REQUEST:
|
||||
return {
|
||||
...state,
|
||||
fetchStatus: FetchStatus.fetching,
|
||||
};
|
||||
case actionType.CLUSTERS__FETCH_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
fetchStatus: FetchStatus.fetched,
|
||||
items: action.payload,
|
||||
};
|
||||
case actionType.CLUSTERS__FETCH_FAILURE:
|
||||
return {
|
||||
...state,
|
||||
fetchStatus: FetchStatus.errorFetching,
|
||||
};
|
||||
|
||||
default:
|
||||
return state;
|
||||
}
|
||||
};
|
||||
|
||||
export default reducer;
|
8
frontend/src/redux/reducers/clusters/selectors.ts
Normal file
8
frontend/src/redux/reducers/clusters/selectors.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import { createSelector } from 'reselect';
|
||||
import { ClustersState, RootState, FetchStatus } from 'types';
|
||||
|
||||
const clustersState = ({ clusters }: RootState): ClustersState => clusters;
|
||||
|
||||
export const getIsClusterListFetched = createSelector(clustersState, ({ fetchStatus }) => fetchStatus === FetchStatus.fetched);
|
||||
|
||||
export const getClusterList = createSelector(clustersState, ({ items }) => items);
|
19
frontend/src/redux/reducers/clusters/thunks.ts
Normal file
19
frontend/src/redux/reducers/clusters/thunks.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import {
|
||||
getClusters,
|
||||
} from 'lib/api';
|
||||
import {
|
||||
fetchClusterListAction,
|
||||
} from './actions';
|
||||
import { Cluster, PromiseThunk } from 'types';
|
||||
|
||||
export const fetchClustersList = (): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(fetchClusterListAction.request());
|
||||
|
||||
try {
|
||||
const clusters: Cluster[] = await getClusters();
|
||||
|
||||
dispatch(fetchClusterListAction.success(clusters));
|
||||
} catch (e) {
|
||||
dispatch(fetchClusterListAction.failure());
|
||||
}
|
||||
}
|
|
@ -1,7 +1,9 @@
|
|||
import { combineReducers } from 'redux';
|
||||
import topics from './topics/reducer';
|
||||
import clusters from './clusters/reducer';
|
||||
import { RootState } from 'types';
|
||||
|
||||
export default combineReducers<RootState>({
|
||||
topics,
|
||||
clusters,
|
||||
});
|
||||
|
|
|
@ -7,14 +7,14 @@ import {
|
|||
fetchTopicListAction,
|
||||
fetchBrokersAction,
|
||||
} from './actions';
|
||||
import { Topic, TopicName, PromiseThunk } from 'types';
|
||||
import { Topic, TopicName, PromiseThunk, ClusterId } from 'types';
|
||||
|
||||
|
||||
export const fetchTopicList = (): PromiseThunk<void> => async (dispatch, getState) => {
|
||||
export const fetchTopicList = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(fetchTopicListAction.request());
|
||||
|
||||
try {
|
||||
const topics = await getTopics();
|
||||
const topics = await getTopics(clusterId);
|
||||
const detailedList = await Promise.all(topics.map((topic: TopicName): Promise<Topic> => getTopic(topic)));
|
||||
|
||||
dispatch(fetchTopicListAction.success(detailedList));
|
||||
|
@ -23,10 +23,10 @@ export const fetchTopicList = (): PromiseThunk<void> => async (dispatch, getStat
|
|||
}
|
||||
}
|
||||
|
||||
export const fetchBrokers = (): PromiseThunk<void> => async (dispatch, getState) => {
|
||||
export const fetchBrokers = (clusterId: ClusterId): PromiseThunk<void> => async (dispatch) => {
|
||||
dispatch(fetchBrokersAction.request());
|
||||
try {
|
||||
const { brokers } = await getBrokers();
|
||||
const { brokers } = await getBrokers(clusterId);
|
||||
dispatch(fetchBrokersAction.success(brokers));
|
||||
} catch (e) {
|
||||
dispatch(fetchBrokersAction.failure());
|
||||
|
|
|
@ -1,13 +1,4 @@
|
|||
@import "../../node_modules/bulma/sass/utilities/_all.sass";
|
||||
|
||||
$menu-item-color: $white;
|
||||
$menu-item-radius: 0;
|
||||
$menu-item-hover-color: $white;
|
||||
$menu-item-hover-background-color: transparent;
|
||||
$menu-item-active-color: $text-strong;
|
||||
$menu-item-active-background-color: $background;
|
||||
$menu-list-border-left: 1px solid $border-light;
|
||||
|
||||
@import "../../node_modules/bulma/sass/base/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/elements/_all.sass";
|
||||
@import "../../node_modules/bulma/sass/form/_all.sass";
|
||||
|
|
|
@ -11,9 +11,32 @@
|
|||
sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
background: repeating-linear-gradient(
|
||||
145deg,
|
||||
rgba(0,0,0,.003),
|
||||
rgba(0,0,0,.005) 5px,
|
||||
rgba(0,0,0,0) 5px,
|
||||
rgba(0,0,0,0) 10px
|
||||
),
|
||||
repeating-linear-gradient(
|
||||
-145deg,
|
||||
rgba(0,0,0,.003),
|
||||
rgba(0,0,0,.005) 5px,
|
||||
rgba(0,0,0,0) 5px,
|
||||
rgba(0,0,0,0) 10px
|
||||
);
|
||||
background-color: $light;
|
||||
|
||||
}
|
||||
|
||||
code {
|
||||
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||
monospace;
|
||||
}
|
||||
|
||||
.has-text-overflow-ellipsis {
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
|
23
frontend/src/types/cluster.ts
Normal file
23
frontend/src/types/cluster.ts
Normal file
|
@ -0,0 +1,23 @@
|
|||
import { FetchStatus } from "types";
|
||||
|
||||
export enum ClusterStatus {
|
||||
Online = 'online',
|
||||
Offline = 'offline',
|
||||
}
|
||||
|
||||
export type ClusterId = string;
|
||||
|
||||
export interface Cluster {
|
||||
id: ClusterId;
|
||||
name: string;
|
||||
defaultCluster: boolean;
|
||||
status: ClusterStatus;
|
||||
brokerCount: number;
|
||||
onlinePartitionCount: number;
|
||||
topicCount: number;
|
||||
}
|
||||
|
||||
export interface ClustersState {
|
||||
fetchStatus: FetchStatus;
|
||||
items: Cluster[];
|
||||
}
|
|
@ -1,9 +1,13 @@
|
|||
import { ActionType } from 'typesafe-actions';
|
||||
import * as topicsActions from 'redux/reducers/topics/actions';
|
||||
import * as clustersActions from 'redux/reducers/clusters/actions';
|
||||
import { ThunkAction } from 'redux-thunk';
|
||||
import { TopicsState } from './topic';
|
||||
import { AnyAction } from 'redux';
|
||||
import { ClustersState } from './cluster';
|
||||
|
||||
export * from './topic';
|
||||
export * from './cluster';
|
||||
|
||||
export enum FetchStatus {
|
||||
notFetched = 'notFetched',
|
||||
|
@ -14,8 +18,9 @@ export enum FetchStatus {
|
|||
|
||||
export interface RootState {
|
||||
topics: TopicsState;
|
||||
clusters: ClustersState;
|
||||
}
|
||||
|
||||
export type Action = ActionType<typeof topicsActions>;
|
||||
export type Action = ActionType<typeof topicsActions | typeof clustersActions>;
|
||||
|
||||
export type PromiseThunk<T> = ThunkAction<Promise<T>, RootState, undefined, AnyAction>;
|
||||
|
|
Loading…
Add table
Reference in a new issue