diff --git a/ui/src/components/GroupSelect.tsx b/ui/src/components/GroupSelect.tsx index ceeebb5..c0866d8 100644 --- a/ui/src/components/GroupSelect.tsx +++ b/ui/src/components/GroupSelect.tsx @@ -25,6 +25,10 @@ const useStyles = makeStyles((theme) => ({ borderRadius: "10px", marginRight: "2px", }, + inputRoot: { + borderRadius: 20, + paddingLeft: "12px !important", + }, })); interface Props { @@ -67,6 +71,10 @@ export default function GroupSelect(props: Props) { renderInput={(params) => ( )} + classes={{ + inputRoot: classes.inputRoot, + }} + size="small" /> ); } diff --git a/ui/src/components/TaskGroupsTable.tsx b/ui/src/components/TaskGroupsTable.tsx index d1cd9e6..6a4d77c 100644 --- a/ui/src/components/TaskGroupsTable.tsx +++ b/ui/src/components/TaskGroupsTable.tsx @@ -1,11 +1,37 @@ import React, { useCallback } from "react"; import { connect, ConnectedProps } from "react-redux"; import { makeStyles } from "@material-ui/core/styles"; +import Checkbox from "@material-ui/core/Checkbox"; +import IconButton from "@material-ui/core/IconButton"; +import Paper from "@material-ui/core/Paper"; +import Table from "@material-ui/core/Table"; +import TableBody from "@material-ui/core/TableBody"; +import TableCell from "@material-ui/core/TableCell"; +import TableContainer from "@material-ui/core/TableContainer"; +import TableFooter from "@material-ui/core/TableFooter"; +import TableHead from "@material-ui/core/TableHead"; +import TablePagination from "@material-ui/core/TablePagination"; +import TableRow from "@material-ui/core/TableRow"; +import Tooltip from "@material-ui/core/Tooltip"; +import ArchiveIcon from "@material-ui/icons/Archive"; +import DeleteIcon from "@material-ui/icons/Delete"; +import MoreHorizIcon from "@material-ui/icons/MoreHoriz"; +import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined"; +import { useHistory } from "react-router-dom"; import { listGroupsAsync } from "../actions/groupsActions"; import GroupSelect from "./GroupSelect"; +import TableActions from "./TableActions"; +import SyntaxHighlighter from "./SyntaxHighlighter"; +import { prettifyPayload, uuidPrefix } from "../utils"; import { usePolling } from "../hooks"; +import { taskDetailsPath } from "../paths"; import { AppState } from "../store"; +import { TaskInfoExtended } from "../reducers/tasksReducer"; import { GroupInfo } from "../api"; +import { TableColumn } from "../types/table"; +import TablePaginationActions, { + rowsPerPageOptions, +} from "./TablePaginationActions"; const useStyles = makeStyles((theme) => ({ groupSelector: { @@ -14,6 +40,19 @@ const useStyles = makeStyles((theme) => ({ paddingLeft: theme.spacing(2), paddingRight: theme.spacing(2), }, + table: { + minWidth: 650, + }, + stickyHeaderCell: { + background: theme.palette.background.paper, + }, + alert: { + borderTopLeftRadius: 0, + borderTopRightRadius: 0, + }, + pagination: { + border: "none", + }, })); function mapStateToProps(state: AppState) { @@ -34,10 +73,21 @@ interface Props { queue: string; } +const columns: TableColumn[] = [ + { key: "id", label: "ID", align: "left" }, + { key: "type", label: "Type", align: "left" }, + { key: "paylod", label: "Payload", align: "left" }, + { key: "actions", label: "Actions", align: "center" }, +]; + +// TODO: remove this once we read the real data. +const dummyTasks: TaskInfoExtended[] = []; + function TaskGroupsTable(props: Props & ConnectedProps) { const [selectedGroup, setSelectedGroup] = React.useState( null ); + const [selectedIds, setSelectedIds] = React.useState([]); const { pollInterval, listGroupsAsync, queue } = props; const classes = useStyles(); @@ -47,6 +97,8 @@ function TaskGroupsTable(props: Props & ConnectedProps) { usePolling(fetchGroups, pollInterval); + const rowCount = 0; // TODO: props.tasks.length; + const numSelected = selectedIds.length; return (
@@ -57,8 +109,279 @@ function TaskGroupsTable(props: Props & ConnectedProps) { error={props.groupsError} />
+ {!window.READ_ONLY && ( + 0} + iconButtonActions={[ + { + tooltip: "Delete", + icon: , + onClick: () => {}, // TODO: handleBatchDeleteClick, + disabled: false, //TODO: props.batchActionPending, + }, + { + tooltip: "Archive", + icon: , + onClick: () => {}, //TODO: handleBatchArchiveClick, + disabled: false, //TODO: props.batchActionPending, + }, + ]} + menuItemActions={[ + { + label: "Delete All", + onClick: () => {}, //TODO: handleDeleteAllClick, + disabled: false, // TODO: props.allActionPending, + }, + { + label: "Archive All", + onClick: () => {}, // TODO: handleArchiveAllClick, + disabled: false, // TODO: props.allActionPending, + }, + ]} + /> + )} + + + + + {!window.READ_ONLY && ( + + + 0 && numSelected < rowCount} + checked={rowCount > 0 && numSelected === rowCount} + onChange={() => {} /*TODO: handleSelectAllClick*/} + inputProps={{ + "aria-label": "select all tasks shown in the table", + }} + /> + + + )} + {columns + .filter((col) => { + // Filter out actions column in readonly mode. + return !window.READ_ONLY || col.key !== "actions"; + }) + .map((col) => ( + + {col.label} + + ))} + + + + { + /*props.tasks */ dummyTasks.map((task) => ( + { + if (checked) { + setSelectedIds(selectedIds.concat(task.id)); + } else { + setSelectedIds( + selectedIds.filter((id) => id !== task.id) + ); + } + }} + allActionPending={false /* TODO: props.allActionPending */} + onDeleteClick={ + () => {} + //TODO: props.deletePendingTaskAsync(queue, task.id) + } + onArchiveClick={() => { + // TODO: props.archivePendingTaskAsync(queue, task.id); + }} + onActionCellEnter={() => {} /*setActiveTaskId(task.id) */} + onActionCellLeave={() => {} /*setActiveTaskId("")*/} + showActions={false /*activeTaskId === task.id*/} + /> + )) + } + + + + {} /* handlePageChange */} + onRowsPerPageChange={() => {} /* handleRowsPerPageChange */} + ActionsComponent={TablePaginationActions} + className={classes.pagination} + /> + + +
+
); } +const useRowStyles = makeStyles((theme) => ({ + root: { + cursor: "pointer", + "& #copy-button": { + display: "none", + }, + "&:hover": { + boxShadow: theme.shadows[2], + "& #copy-button": { + display: "inline-block", + }, + }, + "&:hover $copyButton": { + display: "inline-block", + }, + "&:hover .MuiTableCell-root": { + borderBottomColor: theme.palette.background.paper, + }, + }, + + actionCell: { + width: "96px", + }, + actionButton: { + marginLeft: 3, + marginRight: 3, + }, + idCell: { + width: "200px", + }, + copyButton: { + display: "none", + }, + IdGroup: { + display: "flex", + alignItems: "center", + }, +})); + +interface RowProps { + task: TaskInfoExtended; + isSelected: boolean; + onSelectChange: (checked: boolean) => void; + onDeleteClick: () => void; + onArchiveClick: () => void; + allActionPending: boolean; + showActions: boolean; + onActionCellEnter: () => void; + onActionCellLeave: () => void; +} + +function Row(props: RowProps) { + const { task } = props; + const classes = useRowStyles(); + const history = useHistory(); + return ( + history.push(taskDetailsPath(task.queue, task.id))} + > + {!window.READ_ONLY && ( + e.stopPropagation()}> + + ) => + props.onSelectChange(event.target.checked) + } + checked={props.isSelected} + /> + + + )} + +
+ {uuidPrefix(task.id)} + + { + e.stopPropagation(); + navigator.clipboard.writeText(task.id); + }} + size="small" + className={classes.copyButton} + > + + + +
+
+ {task.type} + + + {prettifyPayload(task.payload)} + + + {task.retried} + {task.max_retry} + {!window.READ_ONLY && ( + e.stopPropagation()} + > + {props.showActions ? ( + + + + + + + + + + + + + ) : ( + + + + )} + + )} +
+ ); +} + export default connector(TaskGroupsTable);