github.com/wfusion/gofusion@v1.1.14/common/infra/asynq/asynqmon/ui/src/components/TasksTableContainer.tsx (about) 1 import React, { useState } from "react"; 2 import { connect, ConnectedProps } from "react-redux"; 3 import { makeStyles } from "@material-ui/core/styles"; 4 import Typography from "@material-ui/core/Typography"; 5 import Paper from "@material-ui/core/Paper"; 6 import Chip from "@material-ui/core/Chip"; 7 import InputBase from "@material-ui/core/InputBase"; 8 import SearchIcon from "@material-ui/icons/Search"; 9 import ActiveTasksTable from "./ActiveTasksTable"; 10 import PendingTasksTable from "./PendingTasksTable"; 11 import ScheduledTasksTable from "./ScheduledTasksTable"; 12 import RetryTasksTable from "./RetryTasksTable"; 13 import ArchivedTasksTable from "./ArchivedTasksTable"; 14 import CompletedTasksTable from "./CompletedTasksTable"; 15 import AggregatingTasksTableContainer from "./AggregatingTasksTableContainer"; 16 import { useHistory } from "react-router-dom"; 17 import { queueDetailsPath, taskDetailsPath } from "../paths"; 18 import { QueueInfo } from "../reducers/queuesReducer"; 19 import { AppState } from "../store"; 20 import { isDarkTheme } from "../theme"; 21 22 interface TabPanelProps { 23 children?: React.ReactNode; 24 selected: string; // currently selected value 25 value: string; // tab panel will be shown if selected value equals to the value 26 } 27 28 function TabPanel(props: TabPanelProps) { 29 const { children, value, selected, ...other } = props; 30 31 return ( 32 <div 33 role="tabpanel" 34 hidden={value !== selected} 35 id={`scrollable-auto-tabpanel-${selected}`} 36 aria-labelledby={`scrollable-auto-tab-${selected}`} 37 style={{ flex: 1, overflowY: "scroll" }} 38 {...other} 39 > 40 {value === selected && children} 41 </div> 42 ); 43 } 44 45 function mapStatetoProps(state: AppState, ownProps: Props) { 46 // TODO: Add loading state for each queue. 47 const queueInfo = state.queues.data.find( 48 (q: QueueInfo) => q.name === ownProps.queue 49 ); 50 const currentStats = queueInfo 51 ? queueInfo.currentStats 52 : { 53 queue: ownProps.queue, 54 paused: false, 55 size: 0, 56 groups: 0, 57 active: 0, 58 pending: 0, 59 aggregating: 0, 60 scheduled: 0, 61 retry: 0, 62 archived: 0, 63 completed: 0, 64 processed: 0, 65 failed: 0, 66 timestamp: "n/a", 67 }; 68 return { currentStats }; 69 } 70 71 const connector = connect(mapStatetoProps); 72 73 type ReduxProps = ConnectedProps<typeof connector>; 74 75 interface Props { 76 queue: string; 77 selected: string; 78 } 79 80 const useStyles = makeStyles((theme) => ({ 81 container: { 82 width: "100%", 83 height: "100%", 84 background: theme.palette.background.paper, 85 }, 86 header: { 87 display: "flex", 88 alignItems: "center", 89 paddingTop: theme.spacing(1), 90 }, 91 heading: { 92 paddingTop: theme.spacing(1), 93 paddingBottom: theme.spacing(1), 94 paddingLeft: theme.spacing(2), 95 paddingRight: theme.spacing(2), 96 }, 97 chip: { 98 marginLeft: theme.spacing(1), 99 }, 100 taskcount: { 101 fontSize: "12px", 102 color: theme.palette.text.secondary, 103 background: isDarkTheme(theme) 104 ? "#303030" 105 : theme.palette.background.default, 106 textAlign: "center", 107 padding: "3px 6px", 108 borderRadius: "10px", 109 marginLeft: "2px", 110 }, 111 searchbar: { 112 paddingLeft: theme.spacing(1), 113 paddingRight: theme.spacing(1), 114 marginRight: theme.spacing(1), 115 flex: 1, 116 }, 117 search: { 118 position: "relative", 119 maxWidth: 400, 120 borderRadius: "18px", 121 backgroundColor: isDarkTheme(theme) ? "#303030" : theme.palette.grey[100], 122 "&:hover, &:focus": { 123 backgroundColor: isDarkTheme(theme) ? "#303030" : theme.palette.grey[200], 124 }, 125 }, 126 searchIcon: { 127 padding: theme.spacing(0, 2), 128 height: "100%", 129 position: "absolute", 130 pointerEvents: "none", 131 display: "flex", 132 alignItems: "center", 133 justifyContent: "center", 134 }, 135 inputRoot: { 136 color: "inherit", 137 width: "100%", 138 }, 139 inputInput: { 140 padding: theme.spacing(1, 1, 1, 0), 141 // vertical padding + font size from searchIcon 142 paddingLeft: `calc(1em + ${theme.spacing(4)}px)`, 143 width: "100%", 144 fontSize: "0.85rem", 145 }, 146 })); 147 148 function TasksTableContainer(props: Props & ReduxProps) { 149 const { currentStats } = props; 150 const classes = useStyles(); 151 const history = useHistory(); 152 const chips = [ 153 { key: "active", label: "Active", count: currentStats.active }, 154 { key: "pending", label: "Pending", count: currentStats.pending }, 155 { 156 key: "aggregating", 157 label: "Aggregating", 158 count: currentStats.aggregating, 159 }, 160 { key: "scheduled", label: "Scheduled", count: currentStats.scheduled }, 161 { key: "retry", label: "Retry", count: currentStats.retry }, 162 { key: "archived", label: "Archived", count: currentStats.archived }, 163 { key: "completed", label: "Completed", count: currentStats.completed }, 164 ]; 165 166 const [searchQuery, setSearchQuery] = useState<string>(""); 167 168 return ( 169 <Paper variant="outlined" className={classes.container}> 170 <div className={classes.header}> 171 <Typography color="textPrimary" className={classes.heading}> 172 Tasks 173 </Typography> 174 <div> 175 {chips.map((c) => ( 176 <Chip 177 key={c.key} 178 className={classes.chip} 179 label={ 180 <div> 181 {c.label} <span className={classes.taskcount}>{c.count}</span> 182 </div> 183 } 184 variant="outlined" 185 color={props.selected === c.key ? "primary" : "default"} 186 onClick={() => history.push(queueDetailsPath(props.queue, c.key))} 187 /> 188 ))} 189 </div> 190 <div className={classes.searchbar}> 191 <div className={classes.search}> 192 <div className={classes.searchIcon}> 193 <SearchIcon /> 194 </div> 195 <InputBase 196 placeholder="Search by ID" 197 classes={{ 198 root: classes.inputRoot, 199 input: classes.inputInput, 200 }} 201 value={searchQuery} 202 onChange={(e) => { 203 setSearchQuery(e.target.value); 204 }} 205 inputProps={{ 206 "aria-label": "search", 207 onKeyDown: (e) => { 208 if (e.key === "Enter") { 209 history.push( 210 taskDetailsPath(props.queue, searchQuery.trim()) 211 ); 212 } 213 }, 214 }} 215 /> 216 </div> 217 </div> 218 </div> 219 <TabPanel value="active" selected={props.selected}> 220 <ActiveTasksTable 221 queue={props.queue} 222 totalTaskCount={currentStats.active} 223 /> 224 </TabPanel> 225 <TabPanel value="pending" selected={props.selected}> 226 <PendingTasksTable 227 queue={props.queue} 228 totalTaskCount={currentStats.pending} 229 /> 230 </TabPanel> 231 <TabPanel value="aggregating" selected={props.selected}> 232 <AggregatingTasksTableContainer queue={props.queue} /> 233 </TabPanel> 234 <TabPanel value="scheduled" selected={props.selected}> 235 <ScheduledTasksTable 236 queue={props.queue} 237 totalTaskCount={currentStats.scheduled} 238 /> 239 </TabPanel> 240 <TabPanel value="retry" selected={props.selected}> 241 <RetryTasksTable 242 queue={props.queue} 243 totalTaskCount={currentStats.retry} 244 /> 245 </TabPanel> 246 <TabPanel value="archived" selected={props.selected}> 247 <ArchivedTasksTable 248 queue={props.queue} 249 totalTaskCount={currentStats.archived} 250 /> 251 </TabPanel> 252 <TabPanel value="completed" selected={props.selected}> 253 <CompletedTasksTable 254 queue={props.queue} 255 totalTaskCount={currentStats.completed} 256 /> 257 </TabPanel> 258 </Paper> 259 ); 260 } 261 262 export default connector(TasksTableContainer);