List.tsx 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257
  1. import React from 'react';
  2. import { useHistory } from 'react-router';
  3. import {
  4. TopicWithDetailedInfo,
  5. ClusterName,
  6. TopicName,
  7. } from 'redux/interfaces';
  8. import { Link, useParams } from 'react-router-dom';
  9. import { clusterTopicNewPath } from 'lib/paths';
  10. import usePagination from 'lib/hooks/usePagination';
  11. import ClusterContext from 'components/contexts/ClusterContext';
  12. import PageLoader from 'components/common/PageLoader/PageLoader';
  13. import Pagination from 'components/common/Pagination/Pagination';
  14. import ConfirmationModal from 'components/common/ConfirmationModal/ConfirmationModal';
  15. import { GetTopicsRequest, TopicColumnsToSort } from 'generated-sources';
  16. import SortableColumnHeader from 'components/common/table/SortableCulumnHeader/SortableColumnHeader';
  17. import Search from 'components/common/Search/Search';
  18. import { PER_PAGE } from 'lib/constants';
  19. import ListItem from './ListItem';
  20. export interface TopicsListProps {
  21. areTopicsFetching: boolean;
  22. topics: TopicWithDetailedInfo[];
  23. totalPages: number;
  24. fetchTopicsList(props: GetTopicsRequest): void;
  25. deleteTopic(topicName: TopicName, clusterName: ClusterName): void;
  26. deleteTopics(topicName: TopicName, clusterNames: ClusterName[]): void;
  27. clearTopicsMessages(topicName: TopicName, clusterNames: ClusterName[]): void;
  28. clearTopicMessages(
  29. topicName: TopicName,
  30. clusterName: ClusterName,
  31. partitions?: number[]
  32. ): void;
  33. search: string;
  34. orderBy: TopicColumnsToSort | null;
  35. setTopicsSearch(search: string): void;
  36. setTopicsOrderBy(orderBy: TopicColumnsToSort | null): void;
  37. }
  38. const List: React.FC<TopicsListProps> = ({
  39. areTopicsFetching,
  40. topics,
  41. totalPages,
  42. fetchTopicsList,
  43. deleteTopic,
  44. deleteTopics,
  45. clearTopicMessages,
  46. clearTopicsMessages,
  47. search,
  48. orderBy,
  49. setTopicsSearch,
  50. setTopicsOrderBy,
  51. }) => {
  52. const { isReadOnly } = React.useContext(ClusterContext);
  53. const { clusterName } = useParams<{ clusterName: ClusterName }>();
  54. const { page, perPage, pathname } = usePagination();
  55. const [showInternal, setShowInternal] = React.useState<boolean>(true);
  56. const history = useHistory();
  57. React.useEffect(() => {
  58. fetchTopicsList({
  59. clusterName,
  60. page,
  61. perPage,
  62. orderBy: orderBy || undefined,
  63. search,
  64. showInternal,
  65. });
  66. }, [
  67. fetchTopicsList,
  68. clusterName,
  69. page,
  70. perPage,
  71. orderBy,
  72. search,
  73. showInternal,
  74. ]);
  75. const handleSwitch = React.useCallback(() => {
  76. setShowInternal(!showInternal);
  77. history.push(`${pathname}?page=1&perPage=${perPage || PER_PAGE}`);
  78. }, [showInternal]);
  79. const [confirmationModal, setConfirmationModal] = React.useState<
  80. '' | 'deleteTopics' | 'purgeMessages'
  81. >('');
  82. const closeConfirmationModal = () => {
  83. setConfirmationModal('');
  84. };
  85. const [selectedTopics, setSelectedTopics] = React.useState<Set<string>>(
  86. new Set()
  87. );
  88. const clearSelectedTopics = () => {
  89. setSelectedTopics(new Set());
  90. };
  91. const toggleTopicSelected = (topicName: string) => {
  92. setSelectedTopics((prevState) => {
  93. const newState = new Set(prevState);
  94. if (newState.has(topicName)) {
  95. newState.delete(topicName);
  96. } else {
  97. newState.add(topicName);
  98. }
  99. return newState;
  100. });
  101. };
  102. const deleteTopicsHandler = React.useCallback(() => {
  103. deleteTopics(clusterName, Array.from(selectedTopics));
  104. closeConfirmationModal();
  105. clearSelectedTopics();
  106. }, [clusterName, selectedTopics]);
  107. const purgeMessagesHandler = React.useCallback(() => {
  108. clearTopicsMessages(clusterName, Array.from(selectedTopics));
  109. closeConfirmationModal();
  110. clearSelectedTopics();
  111. }, [clusterName, selectedTopics]);
  112. return (
  113. <div className="section">
  114. <div className="box">
  115. <div className="columns">
  116. <div className="column is-one-quarter is-align-items-center is-flex">
  117. <div className="field">
  118. <input
  119. id="switchRoundedDefault"
  120. type="checkbox"
  121. name="switchRoundedDefault"
  122. className="switch is-rounded"
  123. checked={showInternal}
  124. onChange={handleSwitch}
  125. />
  126. <label htmlFor="switchRoundedDefault">Show Internal Topics</label>
  127. </div>
  128. </div>
  129. <div className="column">
  130. <Search
  131. handleSearch={setTopicsSearch}
  132. placeholder="Search by Topic Name"
  133. value={search}
  134. />
  135. </div>
  136. <div className="column is-2 is-justify-content-flex-end is-flex">
  137. {!isReadOnly && (
  138. <Link
  139. className="button is-primary"
  140. to={clusterTopicNewPath(clusterName)}
  141. >
  142. Add a Topic
  143. </Link>
  144. )}
  145. </div>
  146. </div>
  147. </div>
  148. {areTopicsFetching ? (
  149. <PageLoader />
  150. ) : (
  151. <div className="box">
  152. {selectedTopics.size > 0 && (
  153. <>
  154. <div className="buttons">
  155. <button
  156. type="button"
  157. className="button is-danger"
  158. onClick={() => {
  159. setConfirmationModal('deleteTopics');
  160. }}
  161. >
  162. Delete selected topics
  163. </button>
  164. <button
  165. type="button"
  166. className="button is-danger"
  167. onClick={() => {
  168. setConfirmationModal('purgeMessages');
  169. }}
  170. >
  171. Purge messages of selected topics
  172. </button>
  173. </div>
  174. <ConfirmationModal
  175. isOpen={confirmationModal !== ''}
  176. onCancel={closeConfirmationModal}
  177. onConfirm={
  178. confirmationModal === 'deleteTopics'
  179. ? deleteTopicsHandler
  180. : purgeMessagesHandler
  181. }
  182. >
  183. {confirmationModal === 'deleteTopics'
  184. ? 'Are you sure you want to remove selected topics?'
  185. : 'Are you sure you want to purge messages of selected topics?'}
  186. </ConfirmationModal>
  187. </>
  188. )}
  189. <table className="table is-fullwidth">
  190. <thead>
  191. <tr>
  192. <th> </th>
  193. <SortableColumnHeader
  194. value={TopicColumnsToSort.NAME}
  195. title="Topic Name"
  196. orderBy={orderBy}
  197. setOrderBy={setTopicsOrderBy}
  198. />
  199. <SortableColumnHeader
  200. value={TopicColumnsToSort.TOTAL_PARTITIONS}
  201. title="Total Partitions"
  202. orderBy={orderBy}
  203. setOrderBy={setTopicsOrderBy}
  204. />
  205. <SortableColumnHeader
  206. value={TopicColumnsToSort.OUT_OF_SYNC_REPLICAS}
  207. title="Out of sync replicas"
  208. orderBy={orderBy}
  209. setOrderBy={setTopicsOrderBy}
  210. />
  211. <th>Replication Factor</th>
  212. <th>Number of messages</th>
  213. <th>Size</th>
  214. <th>Type</th>
  215. <th>Clean Up Policy</th>
  216. <th> </th>
  217. </tr>
  218. </thead>
  219. <tbody>
  220. {topics.map((topic) => (
  221. <ListItem
  222. clusterName={clusterName}
  223. key={topic.name}
  224. topic={topic}
  225. selected={selectedTopics.has(topic.name)}
  226. toggleTopicSelected={toggleTopicSelected}
  227. deleteTopic={deleteTopic}
  228. clearTopicMessages={clearTopicMessages}
  229. />
  230. ))}
  231. {topics.length === 0 && (
  232. <tr>
  233. <td colSpan={10}>No topics found</td>
  234. </tr>
  235. )}
  236. </tbody>
  237. </table>
  238. <Pagination totalPages={totalPages} />
  239. </div>
  240. )}
  241. </div>
  242. );
  243. };
  244. export default List;