mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-01-19 03:05:53 +08:00
Change values displayed in QueuesOverviewTable
This commit is contained in:
parent
a0e7b49c3a
commit
4179d00b7b
@ -8,7 +8,6 @@ import TableCell from "@material-ui/core/TableCell";
|
|||||||
import TableContainer from "@material-ui/core/TableContainer";
|
import TableContainer from "@material-ui/core/TableContainer";
|
||||||
import TableHead from "@material-ui/core/TableHead";
|
import TableHead from "@material-ui/core/TableHead";
|
||||||
import TableRow from "@material-ui/core/TableRow";
|
import TableRow from "@material-ui/core/TableRow";
|
||||||
import TableFooter from "@material-ui/core/TableFooter";
|
|
||||||
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
import TableSortLabel from "@material-ui/core/TableSortLabel";
|
||||||
import IconButton from "@material-ui/core/IconButton";
|
import IconButton from "@material-ui/core/IconButton";
|
||||||
import Tooltip from "@material-ui/core/Tooltip";
|
import Tooltip from "@material-ui/core/Tooltip";
|
||||||
@ -20,34 +19,19 @@ import DeleteQueueConfirmationDialog from "./DeleteQueueConfirmationDialog";
|
|||||||
import { Queue } from "../api";
|
import { Queue } from "../api";
|
||||||
import { queueDetailsPath } from "../paths";
|
import { queueDetailsPath } from "../paths";
|
||||||
import { SortDirection, SortableTableColumn } from "../types/table";
|
import { SortDirection, SortableTableColumn } from "../types/table";
|
||||||
|
import prettyBytes from "pretty-bytes";
|
||||||
|
import { percentage } from "../utils";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
table: {
|
table: {
|
||||||
minWidth: 650,
|
minWidth: 650,
|
||||||
},
|
},
|
||||||
linkCell: {
|
|
||||||
textDecoration: "none",
|
|
||||||
color: theme.palette.text.primary,
|
|
||||||
},
|
|
||||||
footerCell: {
|
|
||||||
fontWeight: 600,
|
|
||||||
fontSize: "0.875rem",
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
boldCell: {
|
|
||||||
fontWeight: 600,
|
|
||||||
},
|
|
||||||
fixedCell: {
|
fixedCell: {
|
||||||
position: "sticky",
|
position: "sticky",
|
||||||
zIndex: 1,
|
zIndex: 1,
|
||||||
left: 0,
|
left: 0,
|
||||||
background: theme.palette.background.paper,
|
background: theme.palette.background.paper,
|
||||||
},
|
},
|
||||||
actionIconsContainer: {
|
|
||||||
display: "flex",
|
|
||||||
justifyContent: "center",
|
|
||||||
minWidth: "100px",
|
|
||||||
},
|
|
||||||
}));
|
}));
|
||||||
|
|
||||||
interface QueueWithMetadata extends Queue {
|
interface QueueWithMetadata extends Queue {
|
||||||
@ -63,32 +47,42 @@ interface Props {
|
|||||||
|
|
||||||
enum SortBy {
|
enum SortBy {
|
||||||
Queue,
|
Queue,
|
||||||
|
State,
|
||||||
Size,
|
Size,
|
||||||
Active,
|
MemoryUsage,
|
||||||
Pending,
|
Processed,
|
||||||
Scheduled,
|
Failed,
|
||||||
Retry,
|
ErrorRate,
|
||||||
Archived,
|
|
||||||
|
|
||||||
None, // no sort support
|
None, // no sort support
|
||||||
}
|
}
|
||||||
|
|
||||||
const colConfigs: SortableTableColumn<SortBy>[] = [
|
const colConfigs: SortableTableColumn<SortBy>[] = [
|
||||||
{ label: "Queue", key: "queue", sortBy: SortBy.Queue, align: "left" },
|
{ label: "Queue", key: "queue", sortBy: SortBy.Queue, align: "left" },
|
||||||
{ label: "Size", key: "size", sortBy: SortBy.Size, align: "right" },
|
{ label: "State", key: "state", sortBy: SortBy.State, align: "left" },
|
||||||
{ label: "Active", key: "active", sortBy: SortBy.Active, align: "right" },
|
|
||||||
{ label: "Pending", key: "pending", sortBy: SortBy.Pending, align: "right" },
|
|
||||||
{
|
{
|
||||||
label: "Scheduled",
|
label: "Size",
|
||||||
key: "scheduled",
|
key: "size",
|
||||||
sortBy: SortBy.Scheduled,
|
sortBy: SortBy.Size,
|
||||||
align: "right",
|
align: "right",
|
||||||
},
|
},
|
||||||
{ label: "Retry", key: "retry", sortBy: SortBy.Retry, align: "right" },
|
|
||||||
{
|
{
|
||||||
label: "Archived",
|
label: "Memory usage",
|
||||||
key: "archived",
|
key: "memory_usage",
|
||||||
sortBy: SortBy.Archived,
|
sortBy: SortBy.MemoryUsage,
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: "Processed",
|
||||||
|
key: "processed",
|
||||||
|
sortBy: SortBy.Processed,
|
||||||
|
align: "right",
|
||||||
|
},
|
||||||
|
{ label: "Failed", key: "failed", sortBy: SortBy.Failed, align: "right" },
|
||||||
|
{
|
||||||
|
label: "Error rate",
|
||||||
|
key: "error_rate",
|
||||||
|
sortBy: SortBy.ErrorRate,
|
||||||
align: "right",
|
align: "right",
|
||||||
},
|
},
|
||||||
{ label: "Actions", key: "actions", sortBy: SortBy.None, align: "center" },
|
{ label: "Actions", key: "actions", sortBy: SortBy.None, align: "center" },
|
||||||
@ -109,12 +103,9 @@ export default function QueuesOverviewTable(props: Props) {
|
|||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const [sortBy, setSortBy] = useState<SortBy>(SortBy.Queue);
|
const [sortBy, setSortBy] = useState<SortBy>(SortBy.Queue);
|
||||||
const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc);
|
const [sortDir, setSortDir] = useState<SortDirection>(SortDirection.Asc);
|
||||||
const [activeRowIndex, setActiveRowIndex] = useState<number>(-1);
|
|
||||||
const [queueToDelete, setQueueToDelete] = useState<QueueWithMetadata | null>(
|
const [queueToDelete, setQueueToDelete] = useState<QueueWithMetadata | null>(
|
||||||
null
|
null
|
||||||
);
|
);
|
||||||
const total = getAggregateCounts(props.queues);
|
|
||||||
|
|
||||||
const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => {
|
const createSortClickHandler = (sortKey: SortBy) => (e: React.MouseEvent) => {
|
||||||
if (sortKey === sortBy) {
|
if (sortKey === sortBy) {
|
||||||
// Toggle sort direction.
|
// Toggle sort direction.
|
||||||
@ -134,29 +125,31 @@ export default function QueuesOverviewTable(props: Props) {
|
|||||||
if (q1.queue === q2.queue) return 0;
|
if (q1.queue === q2.queue) return 0;
|
||||||
isQ1Smaller = q1.queue < q2.queue;
|
isQ1Smaller = q1.queue < q2.queue;
|
||||||
break;
|
break;
|
||||||
|
case SortBy.State:
|
||||||
|
if (q1.paused === q2.paused) return 0;
|
||||||
|
isQ1Smaller = !q1.paused;
|
||||||
|
break;
|
||||||
case SortBy.Size:
|
case SortBy.Size:
|
||||||
if (q1.size === q2.size) return 0;
|
if (q1.size === q2.size) return 0;
|
||||||
isQ1Smaller = q1.size < q2.size;
|
isQ1Smaller = q1.size < q2.size;
|
||||||
break;
|
break;
|
||||||
case SortBy.Active:
|
case SortBy.MemoryUsage:
|
||||||
if (q1.active === q2.active) return 0;
|
if (q1.memory_usage_bytes === q2.memory_usage_bytes) return 0;
|
||||||
isQ1Smaller = q1.active < q2.active;
|
isQ1Smaller = q1.memory_usage_bytes < q2.memory_usage_bytes;
|
||||||
break;
|
break;
|
||||||
case SortBy.Pending:
|
case SortBy.Processed:
|
||||||
if (q1.pending === q2.pending) return 0;
|
if (q1.processed === q2.processed) return 0;
|
||||||
isQ1Smaller = q1.pending < q2.pending;
|
isQ1Smaller = q1.processed < q2.processed;
|
||||||
break;
|
break;
|
||||||
case SortBy.Scheduled:
|
case SortBy.Failed:
|
||||||
if (q1.scheduled === q2.scheduled) return 0;
|
if (q1.failed === q2.failed) return 0;
|
||||||
isQ1Smaller = q1.scheduled < q2.scheduled;
|
isQ1Smaller = q1.failed < q2.failed;
|
||||||
break;
|
break;
|
||||||
case SortBy.Retry:
|
case SortBy.ErrorRate:
|
||||||
if (q1.retry === q2.retry) return 0;
|
const q1ErrorRate = q1.failed / q1.processed;
|
||||||
isQ1Smaller = q1.retry < q2.retry;
|
const q2ErrorRate = q2.failed / q2.processed;
|
||||||
break;
|
if (q1ErrorRate === q2ErrorRate) return 0;
|
||||||
case SortBy.Archived:
|
isQ1Smaller = q1ErrorRate < q2ErrorRate;
|
||||||
if (q1.archived === q2.archived) return 0;
|
|
||||||
isQ1Smaller = q1.archived < q2.archived;
|
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
// eslint-disable-next-line no-throw-literal
|
// eslint-disable-next-line no-throw-literal
|
||||||
@ -201,141 +194,15 @@ export default function QueuesOverviewTable(props: Props) {
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableHead>
|
</TableHead>
|
||||||
<TableBody>
|
<TableBody>
|
||||||
{sortQueues(props.queues, cmpFunc).map((q, i) => (
|
{sortQueues(props.queues, cmpFunc).map((q) => (
|
||||||
<TableRow
|
<Row
|
||||||
key={q.queue}
|
queue={q}
|
||||||
onMouseEnter={() => setActiveRowIndex(i)}
|
onPauseClick={() => props.onPauseClick(q.queue)}
|
||||||
onMouseLeave={() => setActiveRowIndex(-1)}
|
onResumeClick={() => props.onResumeClick(q.queue)}
|
||||||
>
|
onDeleteClick={() => setQueueToDelete(q)}
|
||||||
<TableCell
|
/>
|
||||||
component="th"
|
|
||||||
scope="row"
|
|
||||||
className={clsx(classes.boldCell, classes.fixedCell)}
|
|
||||||
>
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue)}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.queue}
|
|
||||||
{q.paused ? " (paused)" : ""}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right" className={classes.boldCell}>
|
|
||||||
{q.size}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue, "active")}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.active}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue, "pending")}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.pending}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue, "scheduled")}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.scheduled}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue, "retry")}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.retry}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="right">
|
|
||||||
<Link
|
|
||||||
to={queueDetailsPath(q.queue, "archived")}
|
|
||||||
className={classes.linkCell}
|
|
||||||
>
|
|
||||||
{q.archived}
|
|
||||||
</Link>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell align="center">
|
|
||||||
<div className={classes.actionIconsContainer}>
|
|
||||||
{activeRowIndex === i ? (
|
|
||||||
<React.Fragment>
|
|
||||||
{q.paused ? (
|
|
||||||
<Tooltip title="Resume">
|
|
||||||
<IconButton
|
|
||||||
color="secondary"
|
|
||||||
onClick={() => props.onResumeClick(q.queue)}
|
|
||||||
disabled={q.requestPending}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<PlayCircleFilledIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
) : (
|
|
||||||
<Tooltip title="Pause">
|
|
||||||
<IconButton
|
|
||||||
color="primary"
|
|
||||||
onClick={() => props.onPauseClick(q.queue)}
|
|
||||||
disabled={q.requestPending}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<PauseCircleFilledIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
)}
|
|
||||||
<Tooltip title="Delete">
|
|
||||||
<IconButton
|
|
||||||
onClick={() => setQueueToDelete(q)}
|
|
||||||
size="small"
|
|
||||||
>
|
|
||||||
<DeleteIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</React.Fragment>
|
|
||||||
) : (
|
|
||||||
<IconButton size="small">
|
|
||||||
<MoreHorizIcon fontSize="small" />
|
|
||||||
</IconButton>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
))}
|
))}
|
||||||
</TableBody>
|
</TableBody>
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell
|
|
||||||
className={clsx(classes.fixedCell, classes.footerCell)}
|
|
||||||
>
|
|
||||||
Total
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.size}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.active}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.pending}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.scheduled}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.retry}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell className={classes.footerCell} align="right">
|
|
||||||
{total.archived}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
</Table>
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
<DeleteQueueConfirmationDialog
|
<DeleteQueueConfirmationDialog
|
||||||
@ -346,31 +213,122 @@ export default function QueuesOverviewTable(props: Props) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
interface AggregateCounts {
|
const useRowStyles = makeStyles((theme) => ({
|
||||||
size: number;
|
row: {
|
||||||
active: number;
|
"&:last-child td": {
|
||||||
pending: number;
|
borderBottomWidth: 0,
|
||||||
scheduled: number;
|
},
|
||||||
retry: number;
|
"&:last-child th": {
|
||||||
archived: number;
|
borderBottomWidth: 0,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
linkText: {
|
||||||
|
textDecoration: "none",
|
||||||
|
color: theme.palette.text.primary,
|
||||||
|
"&:hover": {
|
||||||
|
textDecoration: "underline",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
textGreen: {
|
||||||
|
color: theme.palette.success.dark,
|
||||||
|
},
|
||||||
|
textRed: {
|
||||||
|
color: theme.palette.error.dark,
|
||||||
|
},
|
||||||
|
boldCell: {
|
||||||
|
fontWeight: 600,
|
||||||
|
},
|
||||||
|
fixedCell: {
|
||||||
|
position: "sticky",
|
||||||
|
zIndex: 1,
|
||||||
|
left: 0,
|
||||||
|
background: theme.palette.background.paper,
|
||||||
|
},
|
||||||
|
actionIconsContainer: {
|
||||||
|
display: "flex",
|
||||||
|
justifyContent: "center",
|
||||||
|
minWidth: "100px",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
interface RowProps {
|
||||||
|
queue: QueueWithMetadata;
|
||||||
|
onPauseClick: () => void;
|
||||||
|
onResumeClick: () => void;
|
||||||
|
onDeleteClick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAggregateCounts(queues: Queue[]): AggregateCounts {
|
function Row(props: RowProps) {
|
||||||
let total = {
|
const classes = useRowStyles();
|
||||||
size: 0,
|
const { queue: q } = props;
|
||||||
active: 0,
|
const [showIcons, setShowIcons] = useState<boolean>(false);
|
||||||
pending: 0,
|
return (
|
||||||
scheduled: 0,
|
<TableRow key={q.queue} className={classes.row}>
|
||||||
retry: 0,
|
<TableCell
|
||||||
archived: 0,
|
component="th"
|
||||||
};
|
scope="row"
|
||||||
queues.forEach((q) => {
|
className={clsx(classes.boldCell, classes.fixedCell)}
|
||||||
total.size += q.size;
|
>
|
||||||
total.active += q.active;
|
<Link to={queueDetailsPath(q.queue)} className={classes.linkText}>
|
||||||
total.pending += q.pending;
|
{q.queue}
|
||||||
total.scheduled += q.scheduled;
|
</Link>
|
||||||
total.retry += q.retry;
|
</TableCell>
|
||||||
total.archived += q.archived;
|
<TableCell>
|
||||||
});
|
{q.paused ? (
|
||||||
return total;
|
<span className={classes.textRed}>paused</span>
|
||||||
|
) : (
|
||||||
|
<span className={classes.textGreen}>run</span>
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
<TableCell align="right">{q.size}</TableCell>
|
||||||
|
<TableCell align="right">{prettyBytes(q.memory_usage_bytes)}</TableCell>
|
||||||
|
<TableCell align="right">{q.processed}</TableCell>
|
||||||
|
<TableCell align="right">{q.failed}</TableCell>
|
||||||
|
<TableCell align="right">{percentage(q.failed, q.processed)}</TableCell>
|
||||||
|
<TableCell
|
||||||
|
align="center"
|
||||||
|
onMouseEnter={() => setShowIcons(true)}
|
||||||
|
onMouseLeave={() => setShowIcons(false)}
|
||||||
|
>
|
||||||
|
<div className={classes.actionIconsContainer}>
|
||||||
|
{showIcons ? (
|
||||||
|
<React.Fragment>
|
||||||
|
{q.paused ? (
|
||||||
|
<Tooltip title="Resume">
|
||||||
|
<IconButton
|
||||||
|
color="secondary"
|
||||||
|
onClick={props.onResumeClick}
|
||||||
|
disabled={q.requestPending}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<PlayCircleFilledIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
) : (
|
||||||
|
<Tooltip title="Pause">
|
||||||
|
<IconButton
|
||||||
|
color="primary"
|
||||||
|
onClick={props.onPauseClick}
|
||||||
|
disabled={q.requestPending}
|
||||||
|
size="small"
|
||||||
|
>
|
||||||
|
<PauseCircleFilledIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
|
<Tooltip title="Delete">
|
||||||
|
<IconButton onClick={props.onDeleteClick} size="small">
|
||||||
|
<DeleteIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</React.Fragment>
|
||||||
|
) : (
|
||||||
|
<IconButton size="small">
|
||||||
|
<MoreHorizIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user