[FE] Fix eslint errors

This commit is contained in:
Azat Mutigullin 2020-11-11 12:36:44 +03:00
parent fb67b538c6
commit cba12d4ff4
44 changed files with 2922 additions and 2400 deletions

View file

@ -42,7 +42,8 @@
{ "extensions": [".js", ".jsx", ".ts", ".tsx"] }
],
"jsx-a11y/label-has-associated-control": "off",
"no-param-reassign": [2, { "props": false }]
"no-param-reassign": [2, { "props": false }],
"import/prefer-default-export": "off"
},
"overrides": [
{

File diff suppressed because it is too large Load diff

View file

@ -4,11 +4,12 @@
"private": true,
"dependencies": {
"@types/react-datepicker": "^3.0.2",
"@types/uuid": "^8.3.0",
"bulma": "^0.8.0",
"bulma-switch": "^2.0.0",
"classnames": "^2.2.6",
"date-fns": "^2.14.0",
"eslint-import-resolver-typescript": "^2.0.0",
"eslint-import-resolver-typescript": "^2.3.0",
"immer": "^6.0.5",
"lodash": "^4.17.15",
"pretty-ms": "^6.0.1",
@ -24,25 +25,28 @@
"redux-thunk": "^2.3.0",
"reselect": "^4.0.0",
"typesafe-actions": "^5.1.0",
"use-debounce": "^3.4.3"
"use-debounce": "^3.4.3",
"uuid": "^8.3.1"
},
"lint-staged": {
"*.{js,ts,jsx,tsx}": [
"eslint -c .eslintrc.json --fix"
"eslint -c .eslintrc.json --fix",
"git add"
]
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"lint": "esprint check",
"lint:fix": "esprint check --fix",
"lint": "eslint --ext .tsx,.ts src/",
"lint:fix": "eslint --ext .tsx,.ts src/ --fix",
"test": "react-scripts test",
"eject": "react-scripts eject",
"mock": "node ./mock/index.js"
"mock": "node ./mock/index.js",
"tsc": "tsc"
},
"husky": {
"hooks": {
"pre-commit": "yarn tsc --noEmit && lint-staged"
"pre-commit": "npm run tsc --noEmit && lint-staged"
}
},
"eslintConfig": {
@ -74,24 +78,24 @@
"@types/react-router-dom": "^5.1.3",
"@types/redux": "^3.6.0",
"@types/redux-thunk": "^2.1.0",
"@typescript-eslint/eslint-plugin": "^2.27.0",
"@typescript-eslint/parser": "^2.27.0",
"@typescript-eslint/eslint-plugin": "^2.34.0",
"@typescript-eslint/parser": "^2.34.0",
"dotenv": "^8.2.0",
"eslint": "^6.8.0",
"eslint-config-airbnb": "^18.1.0",
"eslint-config-prettier": "^6.10.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.19.0",
"eslint-config-airbnb": "^18.2.1",
"eslint-config-prettier": "^6.15.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-prettier": "^3.1.4",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^2.5.1",
"esprint": "^0.6.0",
"husky": "^4.2.5",
"husky": "^4.3.0",
"json-server": "^0.15.1",
"lint-staged": ">=10",
"lint-staged": "^10.5.1",
"node-sass": "^4.13.1",
"prettier": "^2.0.4",
"react-scripts": "3.4.0",
"prettier": "^2.1.2",
"react-scripts": "3.4.4",
"typescript": "~3.7.4"
},
"proxy": "http://localhost:8080"

View file

@ -1,10 +1,8 @@
import { connect } from 'react-redux';
import {
fetchClustersList,
} from 'redux/actions';
import App from './App';
import { fetchClustersList } from 'redux/actions';
import { getIsClusterListFetched } from 'redux/reducers/clusters/selectors';
import { RootState } from 'redux/interfaces';
import App from './App';
const mapStateToProps = (state: RootState) => ({
isClusterListFetched: getIsClusterListFetched(state),
@ -12,6 +10,6 @@ const mapStateToProps = (state: RootState) => ({
const mapDispatchToProps = {
fetchClustersList,
}
};
export default connect(mapStateToProps, mapDispatchToProps)(App);

View file

@ -27,15 +27,14 @@ const Topics: React.FC<Props> = ({
fetchBrokers,
fetchBrokerMetrics,
}) => {
React.useEffect(
() => {
fetchBrokers(clusterName);
fetchBrokerMetrics(clusterName);
},
[fetchBrokers, fetchBrokerMetrics, clusterName],
);
React.useEffect(() => {
fetchBrokers(clusterName);
fetchBrokerMetrics(clusterName);
}, [fetchBrokers, fetchBrokerMetrics, clusterName]);
useInterval(() => { fetchBrokerMetrics(clusterName); }, 5000);
useInterval(() => {
fetchBrokerMetrics(clusterName);
}, 5000);
const zkOnline = zooKeeperStatus === ZooKeeperStatus.online;
@ -44,12 +43,8 @@ const Topics: React.FC<Props> = ({
<Breadcrumb>Brokers overview</Breadcrumb>
<MetricsWrapper title="Uptime">
<Indicator label="Total Brokers">
{brokerCount}
</Indicator>
<Indicator label="Active Controllers">
{activeControllers}
</Indicator>
<Indicator label="Total Brokers">{brokerCount}</Indicator>
<Indicator label="Active Controllers">{activeControllers}</Indicator>
<Indicator label="Zookeeper Status">
<span className={cx('tag', zkOnline ? 'is-primary' : 'is-danger')}>
{zkOnline ? 'Online' : 'Offline'}
@ -59,17 +54,21 @@ const Topics: React.FC<Props> = ({
<MetricsWrapper title="Partitions">
<Indicator label="Online">
<span className={cx({'has-text-danger': offlinePartitionCount !== 0})}>
<span
className={cx({ 'has-text-danger': offlinePartitionCount !== 0 })}
>
{onlinePartitionCount}
</span>
<span className="subtitle has-text-weight-light"> of {onlinePartitionCount + offlinePartitionCount}</span>
<span className="subtitle has-text-weight-light">
{' '}
of
{onlinePartitionCount + offlinePartitionCount}
</span>
</Indicator>
<Indicator label="URP" title="Under replicated partitions">
{underReplicatedPartitionCount}
</Indicator>
<Indicator label="In Sync Replicas">
{inSyncReplicasCount}
</Indicator>
<Indicator label="In Sync Replicas">{inSyncReplicasCount}</Indicator>
<Indicator label="Out of Sync Replicas">
{outOfSyncReplicasCount}
</Indicator>

View file

@ -1,20 +1,24 @@
import { connect } from 'react-redux';
import {
fetchBrokers,
fetchBrokerMetrics,
} from 'redux/actions';
import Brokers from './Brokers';
import { fetchBrokers, fetchBrokerMetrics } from 'redux/actions';
import * as brokerSelectors from 'redux/reducers/brokers/selectors';
import { RootState, ClusterName } from 'redux/interfaces';
import { RouteComponentProps } from 'react-router-dom';
import Brokers from './Brokers';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
isFetched: brokerSelectors.getIsBrokerListFetched(state),
clusterName,
brokerCount: brokerSelectors.getBrokerCount(state),
@ -24,12 +28,15 @@ const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}
offlinePartitionCount: brokerSelectors.getOfflinePartitionCount(state),
inSyncReplicasCount: brokerSelectors.getInSyncReplicasCount(state),
outOfSyncReplicasCount: brokerSelectors.getOutOfSyncReplicasCount(state),
underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(state)
underReplicatedPartitionCount: brokerSelectors.getUnderReplicatedPartitionCount(
state
),
});
const mapDispatchToProps = {
fetchBrokers: (clusterName: ClusterName) => fetchBrokers(clusterName),
fetchBrokerMetrics: (clusterName: ClusterName) => fetchBrokerMetrics(clusterName),
fetchBrokerMetrics: (clusterName: ClusterName) =>
fetchBrokerMetrics(clusterName),
};
export default connect(mapStateToProps, mapDispatchToProps)(Brokers);

View file

@ -5,20 +5,27 @@ import { RouteComponentProps } from 'react-router-dom';
import ConsumerGroups from './ConsumerGroups';
import { getIsConsumerGroupsListFetched } from '../../redux/reducers/consumerGroups/selectors';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
isFetched: getIsConsumerGroupsListFetched(state),
clusterName,
});
const mapDispatchToProps = {
fetchConsumerGroupsList: (clusterName: ClusterName) => fetchConsumerGroupsList(clusterName),
fetchConsumerGroupsList: (clusterName: ClusterName) =>
fetchConsumerGroupsList(clusterName),
};
export default connect(mapStateToProps, mapDispatchToProps)(ConsumerGroups);

View file

@ -1,27 +1,40 @@
import { connect } from 'react-redux';
import Details from './Details';
import {ClusterName, RootState} from 'redux/interfaces';
import { ClusterName, RootState } from 'redux/interfaces';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { getIsConsumerGroupDetailsFetched, getConsumerGroupByID } from 'redux/reducers/consumerGroups/selectors';
import {
getIsConsumerGroupDetailsFetched,
getConsumerGroupByID,
} from 'redux/reducers/consumerGroups/selectors';
import { ConsumerGroupID } from 'redux/interfaces/consumerGroup';
import { fetchConsumerGroupDetails } from 'redux/actions/thunks';
import Details from './Details';
interface RouteProps {
clusterName: ClusterName;
consumerGroupID: string;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { consumerGroupID, clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { consumerGroupID, clusterName },
},
}: OwnProps
) => ({
clusterName,
consumerGroupID,
isFetched: getIsConsumerGroupDetailsFetched(state),
...getConsumerGroupByID(state, consumerGroupID)
...getConsumerGroupByID(state, consumerGroupID),
});
const mapDispatchToProps = {
fetchConsumerGroupDetails: (clusterName: ClusterName, consumerGroupID: ConsumerGroupID) => fetchConsumerGroupDetails(clusterName, consumerGroupID),
fetchConsumerGroupDetails: (
clusterName: ClusterName,
consumerGroupID: ConsumerGroupID
) => fetchConsumerGroupDetails(clusterName, consumerGroupID),
};
export default withRouter(

View file

@ -1,20 +1,25 @@
import { connect } from 'react-redux';
import {ClusterName, RootState} from 'redux/interfaces';
import { ClusterName, RootState } from 'redux/interfaces';
import { getConsumerGroupsList } from 'redux/reducers/consumerGroups/selectors';
import List from './List';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import List from './List';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
clusterName,
consumerGroups: getConsumerGroupsList(state)
consumerGroups: getConsumerGroupsList(state),
});
export default withRouter(
connect(mapStateToProps)(List)
);
export default withRouter(connect(mapStateToProps)(List));

View file

@ -4,23 +4,28 @@ import formatBytes from 'lib/utils/formatBytes';
import { NavLink } from 'react-router-dom';
import { clusterBrokersPath } from 'lib/paths';
const ClusterWidget: React.FC<Cluster> = ({
name,
status,
topicCount,
brokerCount,
bytesInPerSec,
bytesOutPerSec,
onlinePartitionCount,
interface ClusterWidgetProps {
cluster: Cluster;
}
const ClusterWidget: React.FC<ClusterWidgetProps> = ({
cluster: {
name,
status,
topicCount,
brokerCount,
bytesInPerSec,
bytesOutPerSec,
onlinePartitionCount,
},
}) => (
<NavLink to={clusterBrokersPath(name)} className="column is-full-modile is-6">
<div className="box is-hoverable">
<div
className="title is-6 has-text-overflow-ellipsis"
title={name}
>
<div className="title is-6 has-text-overflow-ellipsis" title={name}>
<div
className={`tag has-margin-right ${status === ClusterStatus.Online ? 'is-primary' : 'is-danger'}`}
className={`tag has-margin-right ${
status === ClusterStatus.Online ? 'is-primary' : 'is-danger'
}`}
>
{status}
</div>

View file

@ -1,5 +1,6 @@
import React from 'react';
import { chunk } from 'lodash';
import { v4 } from 'uuid';
import { Cluster } from 'redux/interfaces';
import MetricsWrapper from 'components/common/Dashboard/MetricsWrapper';
import Indicator from 'components/common/Dashboard/Indicator';
@ -19,35 +20,27 @@ const ClustersWidget: React.FC<Props> = ({
const [showOfflineOnly, setShowOfflineOnly] = React.useState<boolean>(false);
const clusterList: Array<Cluster[]> = React.useMemo(() => {
let list = clusters;
let list = clusters;
if (showOfflineOnly) {
list = offlineClusters;
}
if (showOfflineOnly) {
list = offlineClusters;
}
return chunk(list, 2);
},
[clusters, offlineClusters, showOfflineOnly],
);
return chunk(list, 2);
}, [clusters, offlineClusters, showOfflineOnly]);
const handleSwitch = () => setShowOfflineOnly(!showOfflineOnly);
return (
<div>
<h5 className="title is-5">
Clusters
</h5>
<h5 className="title is-5">Clusters</h5>
<MetricsWrapper>
<Indicator label="Online Clusters" >
<span className="tag is-primary">
{onlineClusters.length}
</span>
<Indicator label="Online Clusters">
<span className="tag is-primary">{onlineClusters.length}</span>
</Indicator>
<Indicator label="Offline Clusters">
<span className="tag is-danger">
{offlineClusters.length}
</span>
<span className="tag is-danger">{offlineClusters.length}</span>
</Indicator>
<Indicator label="Hide online clusters">
<input
@ -58,20 +51,19 @@ const ClustersWidget: React.FC<Props> = ({
checked={showOfflineOnly}
onChange={handleSwitch}
/>
<label htmlFor="switchRoundedDefault">
</label>
<label htmlFor="switchRoundedDefault" />
</Indicator>
</MetricsWrapper>
{clusterList.map((chunk, idx) => (
<div className="columns" key={`dashboard-cluster-list-row-key-${idx}`}>
{chunk.map((cluster, idx) => (
<ClusterWidget {...cluster} key={`dashboard-cluster-list-item-key-${idx}`}/>
{clusterList.map((chunkItem) => (
<div className="columns" key={v4()}>
{chunkItem.map((cluster) => (
<ClusterWidget cluster={cluster} key={cluster.id} />
))}
</div>
))}
</div>
)
);
};
export default ClustersWidget;

View file

@ -1,11 +1,11 @@
import { connect } from 'react-redux';
import ClustersWidget from './ClustersWidget';
import {
getClusterList,
getOnlineClusters,
getOfflineClusters,
} from 'redux/reducers/clusters/selectors';
import { RootState } from 'redux/interfaces';
import ClustersWidget from './ClustersWidget';
const mapStateToProps = (state: RootState) => ({
clusters: getClusterList(state),

View file

@ -1,7 +1,10 @@
import { connect } from 'react-redux';
import Nav from './Nav';
import { getIsClusterListFetched, getClusterList } from 'redux/reducers/clusters/selectors';
import {
getIsClusterListFetched,
getClusterList,
} from 'redux/reducers/clusters/selectors';
import { RootState } from 'redux/interfaces';
import Nav from './Nav';
const mapStateToProps = (state: RootState) => ({
isClusterListFetched: getIsClusterListFetched(state),

View file

@ -1,20 +1,25 @@
import { connect } from 'react-redux';
import Details from './Details';
import {ClusterName, RootState} from 'redux/interfaces';
import { ClusterName, RootState } from 'redux/interfaces';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import Details from './Details';
interface RouteProps {
clusterName: ClusterName;
topicName: string;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { topicName, clusterName },
},
}: OwnProps
) => ({
clusterName,
topicName,
});
export default withRouter(
connect(mapStateToProps)(Details)
);
export default withRouter(connect(mapStateToProps)(Details));

View file

@ -177,7 +177,7 @@ const Messages: React.FC<Props> = ({
}, [clusterName, topicName, queryParams]);
const getTimestampDate = (timestamp: string) => {
if (!Date.parse(timestamp)) return;
if (!Date.parse(timestamp)) return null;
return format(Date.parse(timestamp), 'yyyy-MM-dd HH:mm:ss');
};
@ -359,7 +359,7 @@ const Messages: React.FC<Props> = ({
</div>
</div>
<div className="columns">
<div className="column is-full" style={{textAlign: "right"}}>
<div className="column is-full" style={{ textAlign: 'right' }}>
<input
type="submit"
className="button is-primary"

View file

@ -1,20 +1,28 @@
import { connect } from 'react-redux';
import {
fetchTopicDetails,
} from 'redux/actions';
import Overview from './Overview';
import { fetchTopicDetails } from 'redux/actions';
import { RootState, TopicName, ClusterName } from 'redux/interfaces';
import { getTopicByName, getIsTopicDetailsFetched } from 'redux/reducers/topics/selectors';
import {
getTopicByName,
getIsTopicDetailsFetched,
} from 'redux/reducers/topics/selectors';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import Overview from './Overview';
interface RouteProps {
clusterName: ClusterName;
topicName: TopicName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { topicName, clusterName },
},
}: OwnProps
) => ({
clusterName,
topicName,
isFetched: getIsTopicDetailsFetched(state),
@ -22,7 +30,8 @@ const mapStateToProps = (state: RootState, { match: { params: { topicName, clust
});
const mapDispatchToProps = {
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) => fetchTopicDetails(clusterName, topicName),
fetchTopicDetails: (clusterName: ClusterName, topicName: TopicName) =>
fetchTopicDetails(clusterName, topicName),
};
export default withRouter(

View file

@ -1,4 +1,5 @@
import React from 'react';
import { v4 } from 'uuid';
import { ClusterName, TopicName, TopicConfig } from 'redux/interfaces';
interface Props {
@ -9,29 +10,24 @@ interface Props {
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => void;
}
const ConfigListItem: React.FC<TopicConfig> = ({
name,
value,
defaultValue,
interface ListItemProps {
config: TopicConfig;
}
const ConfigListItem: React.FC<ListItemProps> = ({
config: { name, value, defaultValue },
}) => {
const hasCustomValue = value !== defaultValue;
return (
<tr>
<td className={hasCustomValue ? 'has-text-weight-bold' : ''}>
{name}
</td>
<td className={hasCustomValue ? 'has-text-weight-bold' : ''}>
{value}
</td>
<td
className="has-text-grey"
title="Default Value"
>
<td className={hasCustomValue ? 'has-text-weight-bold' : ''}>{name}</td>
<td className={hasCustomValue ? 'has-text-weight-bold' : ''}>{value}</td>
<td className="has-text-grey" title="Default Value">
{hasCustomValue && defaultValue}
</td>
</tr>
)
);
};
const Sertings: React.FC<Props> = ({
@ -41,10 +37,9 @@ const Sertings: React.FC<Props> = ({
fetchTopicConfig,
config,
}) => {
React.useEffect(
() => { fetchTopicConfig(clusterName, topicName); },
[fetchTopicConfig, clusterName, topicName],
);
React.useEffect(() => {
fetchTopicConfig(clusterName, topicName);
}, [fetchTopicConfig, clusterName, topicName]);
if (!isFetched || !config) {
return null;
@ -61,7 +56,9 @@ const Sertings: React.FC<Props> = ({
</tr>
</thead>
<tbody>
{config.map((item, index) => <ConfigListItem key={`config-list-item-key-${index}`} {...item} />)}
{config.map((item) => (
<ConfigListItem key={v4()} config={item} />
))}
</tbody>
</table>
</div>

View file

@ -1,24 +1,28 @@
import { connect } from 'react-redux';
import { RootState, ClusterName, TopicName } from 'redux/interfaces';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import {
fetchTopicConfig,
} from 'redux/actions';
import Settings from './Settings';
import { fetchTopicConfig } from 'redux/actions';
import {
getTopicConfig,
getTopicConfigFetched,
} from 'redux/reducers/topics/selectors';
import Settings from './Settings';
interface RouteProps {
clusterName: ClusterName;
topicName: TopicName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { topicName, clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { topicName, clusterName },
},
}: OwnProps
) => ({
clusterName,
topicName,
config: getTopicConfig(state, topicName),
@ -26,7 +30,8 @@ const mapStateToProps = (state: RootState, { match: { params: { topicName, clust
});
const mapDispatchToProps = {
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) => fetchTopicConfig(clusterName, topicName),
fetchTopicConfig: (clusterName: ClusterName, topicName: TopicName) =>
fetchTopicConfig(clusterName, topicName),
};
export default withRouter(

View file

@ -1,21 +1,18 @@
import React from 'react';
import { v4 } from 'uuid';
import { TopicWithDetailedInfo, ClusterName } from 'redux/interfaces';
import ListItem from './ListItem';
import Breadcrumb from 'components/common/Breadcrumb/Breadcrumb';
import { NavLink } from 'react-router-dom';
import { clusterTopicNewPath } from 'lib/paths';
import ListItem from './ListItem';
interface Props {
clusterName: ClusterName;
topics: (TopicWithDetailedInfo)[];
externalTopics: (TopicWithDetailedInfo)[];
topics: TopicWithDetailedInfo[];
externalTopics: TopicWithDetailedInfo[];
}
const List: React.FC<Props> = ({
clusterName,
topics,
externalTopics,
}) => {
const List: React.FC<Props> = ({ clusterName, topics, externalTopics }) => {
const [showInternal, setShowInternal] = React.useState<boolean>(true);
const handleSwitch = () => setShowInternal(!showInternal);
@ -38,9 +35,7 @@ const List: React.FC<Props> = ({
checked={showInternal}
onChange={handleSwitch}
/>
<label htmlFor="switchRoundedDefault">
Show Internal Topics
</label>
<label htmlFor="switchRoundedDefault">Show Internal Topics</label>
</div>
</div>
<div className="level-item level-right">
@ -64,11 +59,8 @@ const List: React.FC<Props> = ({
</tr>
</thead>
<tbody>
{items.map((topic, index) => (
<ListItem
key={`topic-list-item-key-${index}`}
{...topic}
/>
{items.map((topic) => (
<ListItem key={v4()} topic={topic} />
))}
</tbody>
</table>

View file

@ -1,21 +1,29 @@
import { connect } from 'react-redux';
import {ClusterName, RootState} from 'redux/interfaces';
import { getTopicList, getExternalTopicList } from 'redux/reducers/topics/selectors';
import List from './List';
import { ClusterName, RootState } from 'redux/interfaces';
import {
getTopicList,
getExternalTopicList,
} from 'redux/reducers/topics/selectors';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import List from './List';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
clusterName,
topics: getTopicList(state),
externalTopics: getExternalTopicList(state),
});
export default withRouter(
connect(mapStateToProps)(List)
);
export default withRouter(connect(mapStateToProps)(List));

View file

@ -3,10 +3,12 @@ import cx from 'classnames';
import { NavLink } from 'react-router-dom';
import { TopicWithDetailedInfo } from 'redux/interfaces';
const ListItem: React.FC<TopicWithDetailedInfo> = ({
name,
internal,
partitions,
interface ListItemProps {
topic: TopicWithDetailedInfo;
}
const ListItem: React.FC<ListItemProps> = ({
topic: { name, internal, partitions },
}) => {
const outOfSyncReplicas = React.useMemo(() => {
if (partitions === undefined || partitions.length === 0) {
@ -14,27 +16,34 @@ const ListItem: React.FC<TopicWithDetailedInfo> = ({
}
return partitions.reduce((memo: number, { replicas }) => {
const outOfSync = replicas.filter(({ inSync }) => !inSync)
const outOfSync = replicas.filter(({ inSync }) => !inSync);
return memo + outOfSync.length;
}, 0);
}, [partitions])
}, [partitions]);
return (
<tr>
<td>
<NavLink exact to={`topics/${name}`} activeClassName="is-active" className="title is-6">
<NavLink
exact
to={`topics/${name}`}
activeClassName="is-active"
className="title is-6"
>
{name}
</NavLink>
</td>
<td>{partitions.length}</td>
<td>{outOfSyncReplicas}</td>
<td>
<div className={cx('tag is-small', internal ? 'is-light' : 'is-success')}>
<div
className={cx('tag is-small', internal ? 'is-light' : 'is-success')}
>
{internal ? 'Internal' : 'External'}
</div>
</td>
</tr>
);
}
};
export default ListItem;

View file

@ -1,35 +1,48 @@
import { connect } from 'react-redux';
import { RootState, ClusterName, TopicFormData, TopicName, Action } from 'redux/interfaces';
import New from './New';
import {
RootState,
ClusterName,
TopicFormData,
TopicName,
Action,
} from 'redux/interfaces';
import { withRouter, RouteComponentProps } from 'react-router-dom';
import { createTopic } from 'redux/actions';
import { getTopicCreated } from 'redux/reducers/topics/selectors';
import { clusterTopicPath } from 'lib/paths';
import { ThunkDispatch } from 'redux-thunk';
import * as actions from "../../../redux/actions/actions";
import New from './New';
import * as actions from '../../../redux/actions/actions';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } } }: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
clusterName,
isTopicCreated: getTopicCreated(state),
});
const mapDispatchToProps = (dispatch: ThunkDispatch<RootState, undefined, Action>, { history }: OwnProps) => ({
const mapDispatchToProps = (
dispatch: ThunkDispatch<RootState, undefined, Action>,
{ history }: OwnProps
) => ({
createTopic: (clusterName: ClusterName, form: TopicFormData) => {
dispatch(createTopic(clusterName, form));
},
redirectToTopicPath: (clusterName: ClusterName, topicName: TopicName) => {
history.push(clusterTopicPath(clusterName, topicName));
},
resetUploadedState: (() => dispatch(actions.createTopicAction.failure()))
resetUploadedState: () => dispatch(actions.createTopicAction.failure()),
});
export default withRouter(
connect(mapStateToProps, mapDispatchToProps)(New)
);
export default withRouter(connect(mapStateToProps, mapDispatchToProps)(New));

View file

@ -1,17 +1,24 @@
import { connect } from 'react-redux';
import { fetchTopicList } from 'redux/actions';
import Topics from './Topics';
import { getIsTopicListFetched } from 'redux/reducers/topics/selectors';
import { RootState, ClusterName } from 'redux/interfaces';
import { RouteComponentProps } from 'react-router-dom';
import Topics from './Topics';
interface RouteProps {
clusterName: ClusterName;
}
interface OwnProps extends RouteComponentProps<RouteProps> { }
type OwnProps = RouteComponentProps<RouteProps>;
const mapStateToProps = (state: RootState, { match: { params: { clusterName } }}: OwnProps) => ({
const mapStateToProps = (
state: RootState,
{
match: {
params: { clusterName },
},
}: OwnProps
) => ({
isFetched: getIsTopicListFetched(state),
clusterName,
});

View file

@ -1,5 +1,6 @@
import React from 'react';
import { NavLink } from 'react-router-dom';
import { v4 } from 'uuid';
interface Link {
label: string;
@ -10,18 +11,16 @@ interface Props {
links?: Link[];
}
const Breadcrumb: React.FC<Props> = ({
links,
children,
}) => {
const Breadcrumb: React.FC<Props> = ({ links, children }) => {
return (
<nav className="breadcrumb" aria-label="breadcrumbs">
<ul>
{links && links.map(({ label, href }, index) => (
<li key={`breadcrumb-item-key-${index}`}>
<NavLink to={href}>{label}</NavLink>
</li>
))}
{links &&
links.map(({ label, href }) => (
<li key={v4()}>
<NavLink to={href}>{label}</NavLink>
</li>
))}
<li className="is-active">
<span className="">{children}</span>
@ -29,6 +28,6 @@ const Breadcrumb: React.FC<Props> = ({
</ul>
</nav>
);
}
};
export default Breadcrumb;

View file

@ -5,19 +5,15 @@ interface Props {
title?: string;
}
const Indicator: React.FC<Props> = ({
label,
title,
children,
}) => {
const Indicator: React.FC<Props> = ({ label, title, children }) => {
return (
<div className="level-item level-left">
<div title={title ? title : label}>
<div title={title || label}>
<p className="heading">{label}</p>
<p className="title">{children}</p>
</div>
</div>
);
}
};
export default Indicator;

View file

@ -13,16 +13,10 @@ const MetricsWrapper: React.FC<Props> = ({
}) => {
return (
<div className={cx('box', wrapperClassName)}>
{title && (
<h5 className="subtitle is-6">
{title}
</h5>
)}
<div className="level">
{children}
</div>
{title && <h5 className="subtitle is-6">{title}</h5>}
<div className="level">{children}</div>
</div>
);
}
};
export default MetricsWrapper;

View file

@ -1,4 +1,3 @@
import React from 'react';
import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';
@ -16,7 +15,7 @@ ReactDOM.render(
<AppContainer />
</BrowserRouter>
</Provider>,
document.getElementById('root'),
document.getElementById('root')
);
// If you want your app to work offline and load faster, you can change

View file

@ -5,26 +5,21 @@ type Callback = () => any;
const useInterval = (callback: Callback, delay: number) => {
const savedCallback = React.useRef<Callback>();
React.useEffect(
() => {
savedCallback.current = callback;
},
[callback],
);
React.useEffect(() => {
savedCallback.current = callback;
}, [callback]);
React.useEffect(
() => {
const tick = () => {
savedCallback.current && savedCallback.current()
};
// eslint-disable-next-line consistent-return
React.useEffect(() => {
const tick = () => {
if (savedCallback.current) savedCallback.current();
};
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
},
[delay],
);
}
if (delay !== null) {
const id = setInterval(tick, delay);
return () => clearInterval(id);
}
}, [delay]);
};
export default useInterval;

View file

@ -1,13 +1,13 @@
function formatBytes(bytes: number, decimals: number = 0) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (bytes === 0) return [0, sizes[0]];
function formatBytes(bytes: number, decimals = 0) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
if (bytes === 0) return [0, sizes[0]];
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const k = 1024;
const dm = decimals < 0 ? 0 : decimals;
const i = Math.floor(Math.log(bytes) / Math.log(k));
const i = Math.floor(Math.log(bytes) / Math.log(k));
return [parseFloat((bytes / Math.pow(k, i)).toFixed(dm)), sizes[i]];
return [parseFloat((bytes / k ** i).toFixed(dm)), sizes[i]];
}
export default formatBytes;

View file

@ -1 +1 @@
/// <reference types="react-scripts" />
// / <reference types="react-scripts" />

View file

@ -1,11 +1,5 @@
import {
Cluster,
} from 'redux/interfaces';
import {
BASE_URL,
BASE_PARAMS,
} from 'lib/constants';
import { Cluster } from 'redux/interfaces';
import { BASE_URL, BASE_PARAMS } from 'lib/constants';
export const getClusters = (): Promise<Cluster[]> =>
fetch(`${BASE_URL}/clusters`, { ...BASE_PARAMS })
.then(res => res.json());
fetch(`${BASE_URL}/clusters`, { ...BASE_PARAMS }).then((res) => res.json());

View file

@ -1,12 +1,23 @@
import { ClusterName } from '../interfaces/cluster';
import { ConsumerGroup, ConsumerGroupID, ConsumerGroupDetails } from '../interfaces/consumerGroup';
import {
ConsumerGroup,
ConsumerGroupID,
ConsumerGroupDetails,
} from '../interfaces/consumerGroup';
import { BASE_PARAMS, BASE_URL } from '../../lib/constants';
export const getConsumerGroups = (
clusterName: ClusterName
): Promise<ConsumerGroup[]> =>
fetch(`${BASE_URL}/clusters/${clusterName}/consumerGroups`, {
...BASE_PARAMS,
}).then((res) => res.json());
export const getConsumerGroups = (clusterName: ClusterName): Promise<ConsumerGroup[]> =>
fetch(`${BASE_URL}/clusters/${clusterName}/consumerGroups`, { ...BASE_PARAMS })
.then(res => res.json());
export const getConsumerGroupDetails = (clusterName: ClusterName, consumerGroupID: ConsumerGroupID): Promise<ConsumerGroupDetails> =>
fetch(`${BASE_URL}/clusters/${clusterName}/consumer-groups/${consumerGroupID}`, { ...BASE_PARAMS })
.then(res => res.json());
export const getConsumerGroupDetails = (
clusterName: ClusterName,
consumerGroupID: ConsumerGroupID
): Promise<ConsumerGroupDetails> =>
fetch(
`${BASE_URL}/clusters/${clusterName}/consumer-groups/${consumerGroupID}`,
{ ...BASE_PARAMS }
).then((res) => res.json());

View file

@ -6,9 +6,12 @@ export interface Broker {
segmentSize: number;
partitionReplicas: number;
bytesOutPerSec: number;
};
}
export enum ZooKeeperStatus { offline, online };
export enum ZooKeeperStatus {
offline,
online,
}
export interface BrokerDiskUsage {
brokerId: BrokerId;
@ -21,8 +24,8 @@ export interface BrokerMetrics {
activeControllers: number;
onlinePartitionCount: number;
offlinePartitionCount: number;
inSyncReplicasCount: number,
outOfSyncReplicasCount: number,
inSyncReplicasCount: number;
outOfSyncReplicasCount: number;
underReplicatedPartitionCount: number;
diskUsage: BrokerDiskUsage[];
}

View file

@ -33,4 +33,9 @@ export interface RootState {
export type Action = ActionType<typeof actions>;
export type PromiseThunk<T> = ThunkAction<Promise<T>, RootState, undefined, AnyAction>;
export type PromiseThunk<T> = ThunkAction<
Promise<T>,
RootState,
undefined,
AnyAction
>;

View file

@ -1,5 +1,10 @@
import { createSelector } from 'reselect';
import { Cluster, RootState, FetchStatus, ClusterStatus } from 'redux/interfaces';
import {
Cluster,
RootState,
FetchStatus,
ClusterStatus,
} from 'redux/interfaces';
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
const clustersState = ({ clusters }: RootState): Cluster[] => clusters;
@ -8,21 +13,18 @@ const getClusterListFetchingStatus = createFetchingSelector('GET_CLUSTERS');
export const getIsClusterListFetched = createSelector(
getClusterListFetchingStatus,
(status) => status === FetchStatus.fetched,
(status) => status === FetchStatus.fetched
);
export const getClusterList = createSelector(clustersState, (clusters) => clusters);
export const getOnlineClusters = createSelector(
getClusterList,
(clusters) => clusters.filter(
({ status }) => status === ClusterStatus.Online,
),
export const getClusterList = createSelector(
clustersState,
(clusters) => clusters
);
export const getOfflineClusters = createSelector(
getClusterList,
(clusters) => clusters.filter(
({ status }) => status === ClusterStatus.Offline,
),
export const getOnlineClusters = createSelector(getClusterList, (clusters) =>
clusters.filter(({ status }) => status === ClusterStatus.Online)
);
export const getOfflineClusters = createSelector(getClusterList, (clusters) =>
clusters.filter(({ status }) => status === ClusterStatus.Offline)
);

View file

@ -1,25 +1,35 @@
import { createSelector } from 'reselect';
import { RootState, FetchStatus } from 'redux/interfaces';
import { createFetchingSelector } from 'redux/reducers/loader/selectors';
import { ConsumerGroupID, ConsumerGroupsState } from '../../interfaces/consumerGroup';
import {
ConsumerGroupID,
ConsumerGroupsState,
} from '../../interfaces/consumerGroup';
const consumerGroupsState = ({
consumerGroups,
}: RootState): ConsumerGroupsState => consumerGroups;
const consumerGroupsState = ({ consumerGroups }: RootState): ConsumerGroupsState => consumerGroups;
const getConsumerGroupsMap = (state: RootState) =>
consumerGroupsState(state).byID;
const getConsumerGroupsIDsList = (state: RootState) =>
consumerGroupsState(state).allIDs;
const getConsumerGroupsMap = (state: RootState) => consumerGroupsState(state).byID;
const getConsumerGroupsIDsList = (state: RootState) => consumerGroupsState(state).allIDs;
const getConsumerGroupsListFetchingStatus = createFetchingSelector('GET_CONSUMER_GROUPS');
const getConsumerGroupDetailsFetchingStatus = createFetchingSelector('GET_CONSUMER_GROUP_DETAILS');
const getConsumerGroupsListFetchingStatus = createFetchingSelector(
'GET_CONSUMER_GROUPS'
);
const getConsumerGroupDetailsFetchingStatus = createFetchingSelector(
'GET_CONSUMER_GROUP_DETAILS'
);
export const getIsConsumerGroupsListFetched = createSelector(
getConsumerGroupsListFetchingStatus,
(status) => status === FetchStatus.fetched,
(status) => status === FetchStatus.fetched
);
export const getIsConsumerGroupDetailsFetched = createSelector(
getConsumerGroupDetailsFetchingStatus,
(status) => status === FetchStatus.fetched,
(status) => status === FetchStatus.fetched
);
export const getConsumerGroupsList = createSelector(
@ -31,14 +41,15 @@ export const getConsumerGroupsList = createSelector(
return [];
}
return ids.map(key => byID[key]);
},
return ids.map((key) => byID[key]);
}
);
const getConsumerGroupID = (_: RootState, consumerGroupID: ConsumerGroupID) => consumerGroupID;
const getConsumerGroupID = (_: RootState, consumerGroupID: ConsumerGroupID) =>
consumerGroupID;
export const getConsumerGroupByID = createSelector(
getConsumerGroupsMap,
getConsumerGroupID,
(consumerGroups, consumerGroupID) => consumerGroups[consumerGroupID],
(consumerGroups, consumerGroupID) => consumerGroups[consumerGroupID]
);

View file

@ -1,10 +1,10 @@
import { combineReducers } from 'redux';
import { RootState } from 'redux/interfaces';
import topics from './topics/reducer';
import clusters from './clusters/reducer';
import brokers from './brokers/reducer';
import consumerGroups from './consumerGroups/reducer';
import loader from './loader/reducer';
import { RootState } from 'redux/interfaces';
export default combineReducers<RootState>({
topics,

View file

@ -1,8 +1,4 @@
import {
FetchStatus,
Action,
LoaderState,
} from 'redux/interfaces';
import { FetchStatus, Action, LoaderState } from 'redux/interfaces';
export const initialState: LoaderState = {};

View file

@ -1,4 +1,4 @@
import { RootState, FetchStatus } from 'redux/interfaces';
export const createFetchingSelector = (action: string) =>
(state: RootState) => (state.loader[action] || FetchStatus.notFetched);
export const createFetchingSelector = (action: string) => (state: RootState) =>
state.loader[action] || FetchStatus.notFetched;

View file

@ -5,13 +5,12 @@ import rootReducer from '../../reducers';
export default () => {
const middlewares = [thunk];
const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const composeEnhancers =
(window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(
applyMiddleware(...middlewares),
);
const enhancer = composeEnhancers(applyMiddleware(...middlewares));
const store = createStore(rootReducer, undefined, enhancer);
return store
return store;
};

View file

@ -1,6 +1,9 @@
import devConfigureStore from './dev';
import prodConfigureStore from './prod';
const configureStore = (process.env.NODE_ENV === 'production') ? prodConfigureStore : devConfigureStore
const configureStore =
process.env.NODE_ENV === 'production'
? prodConfigureStore
: devConfigureStore;
export default configureStore;

View file

@ -3,11 +3,11 @@ import thunk from 'redux-thunk';
import rootReducer from '../../reducers';
export default () => {
const middlewares = [thunk]
const middlewares = [thunk];
const enhancer = applyMiddleware(...middlewares);
const store = createStore(rootReducer, undefined, enhancer);
return store
return store;
};

View file

@ -25,47 +25,10 @@ type Config = {
onUpdate?: (registration: ServiceWorkerRegistration) => void;
};
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(
process.env.PUBLIC_URL,
window.location.href
);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then(registration => {
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
@ -101,7 +64,7 @@ function registerValidSW(swUrl: string, config?: Config) {
};
};
})
.catch(error => {
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
@ -109,9 +72,9 @@ function registerValidSW(swUrl: string, config?: Config) {
function checkValidServiceWorker(swUrl: string, config?: Config) {
// Check if the service worker can be found. If it can't reload the page.
fetch(swUrl, {
headers: { 'Service-Worker': 'script' }
headers: { 'Service-Worker': 'script' },
})
.then(response => {
.then((response) => {
// Ensure service worker exists, and that we really are getting a JS file.
const contentType = response.headers.get('content-type');
if (
@ -119,7 +82,7 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
(contentType != null && contentType.indexOf('javascript') === -1)
) {
// No service worker found. Probably a different app. Reload the page.
navigator.serviceWorker.ready.then(registration => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister().then(() => {
window.location.reload();
});
@ -136,9 +99,44 @@ function checkValidServiceWorker(swUrl: string, config?: Config) {
});
}
export function register(config?: Config) {
if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
const url = process.env.PUBLIC_URL || 'localhost';
// The URL constructor is available in all browsers that support SW.
const publicUrl = new URL(url, window.location.href);
if (publicUrl.origin !== window.location.origin) {
// Our service worker won't work if PUBLIC_URL is on a different origin
// from what our page is served on. This might happen if a CDN is used to
// serve assets; see https://github.com/facebook/create-react-app/issues/2374
return;
}
window.addEventListener('load', () => {
const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
if (isLocalhost) {
// This is running on localhost. Let's check if a service worker still exists or not.
checkValidServiceWorker(swUrl, config);
// Add some additional logging to localhost, pointing developers to the
// service worker/PWA documentation.
navigator.serviceWorker.ready.then(() => {
console.log(
'This web app is being served cache-first by a service ' +
'worker. To learn more, visit https://bit.ly/CRA-PWA'
);
});
} else {
// Is not localhost. Just register service worker
registerValidSW(swUrl, config);
}
});
}
}
export function unregister() {
if ('serviceWorker' in navigator) {
navigator.serviceWorker.ready.then(registration => {
navigator.serviceWorker.ready.then((registration) => {
registration.unregister();
});
}

View file

@ -2,4 +2,5 @@
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
// eslint-disable-next-line import/no-extraneous-dependencies
import '@testing-library/jest-dom/extend-expect';