mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-01-19 03:05:53 +08:00
Add breadcrumb to task view
This commit is contained in:
parent
b2c8de61bb
commit
2b2d5f88a5
87
ui/src/components/QueueBreadcrumb.tsx
Normal file
87
ui/src/components/QueueBreadcrumb.tsx
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import React, { useState } from "react";
|
||||||
|
import { Link, useHistory } from "react-router-dom";
|
||||||
|
import { emphasize, withStyles, Theme } from "@material-ui/core/styles";
|
||||||
|
import Breadcrumbs from "@material-ui/core/Breadcrumbs";
|
||||||
|
import Chip from "@material-ui/core/Chip";
|
||||||
|
import Menu from "@material-ui/core/Menu";
|
||||||
|
import MenuItem from "@material-ui/core/MenuItem";
|
||||||
|
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
|
||||||
|
import { paths, queueDetailsPath } from "../paths";
|
||||||
|
|
||||||
|
const StyledBreadcrumb = withStyles((theme: Theme) => ({
|
||||||
|
root: {
|
||||||
|
backgroundColor:
|
||||||
|
theme.palette.type === "dark"
|
||||||
|
? "#303030"
|
||||||
|
: theme.palette.background.default,
|
||||||
|
height: theme.spacing(3),
|
||||||
|
color: theme.palette.text.secondary,
|
||||||
|
fontWeight: theme.typography.fontWeightRegular,
|
||||||
|
"&:hover, &:focus": {
|
||||||
|
backgroundColor: theme.palette.action.hover,
|
||||||
|
},
|
||||||
|
"&:active": {
|
||||||
|
boxShadow: theme.shadows[1],
|
||||||
|
backgroundColor: emphasize(theme.palette.action.hover, 0.12),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}))(Chip) as typeof Chip; // Note: need a type cast here because https://github.com/Microsoft/TypeScript/issues/26591
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
// All queue names.
|
||||||
|
queues: string[];
|
||||||
|
// Name of the queue currently selected.
|
||||||
|
selectedQueue: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function QueueBreadcrumbs(props: Props) {
|
||||||
|
const history = useHistory();
|
||||||
|
const [anchorEl, setAnchorEl] = useState<null | Element>(null);
|
||||||
|
|
||||||
|
const handleClick = (event: React.MouseEvent<Element, MouseEvent>) => {
|
||||||
|
event.preventDefault();
|
||||||
|
setAnchorEl(event.currentTarget);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeMenu = () => {
|
||||||
|
setAnchorEl(null);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Breadcrumbs aria-label="breadcrumb">
|
||||||
|
<StyledBreadcrumb
|
||||||
|
component={Link}
|
||||||
|
to={paths.HOME}
|
||||||
|
label="Queues"
|
||||||
|
onClick={() => history.push(paths.HOME)}
|
||||||
|
/>
|
||||||
|
<StyledBreadcrumb
|
||||||
|
label={props.selectedQueue}
|
||||||
|
deleteIcon={<ExpandMoreIcon />}
|
||||||
|
onClick={handleClick}
|
||||||
|
onDelete={handleClick}
|
||||||
|
/>
|
||||||
|
</Breadcrumbs>
|
||||||
|
<Menu
|
||||||
|
id="queue-breadcrumb-menu"
|
||||||
|
anchorEl={anchorEl}
|
||||||
|
keepMounted
|
||||||
|
open={Boolean(anchorEl)}
|
||||||
|
onClose={closeMenu}
|
||||||
|
>
|
||||||
|
{props.queues.sort().map((qname) => (
|
||||||
|
<MenuItem
|
||||||
|
key={qname}
|
||||||
|
onClick={() => {
|
||||||
|
history.push(queueDetailsPath(qname));
|
||||||
|
closeMenu();
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{qname}
|
||||||
|
</MenuItem>
|
||||||
|
))}
|
||||||
|
</Menu>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -3,6 +3,7 @@ import { connect, ConnectedProps } from "react-redux";
|
|||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import Typography from "@material-ui/core/Typography";
|
import Typography from "@material-ui/core/Typography";
|
||||||
import { AppState } from "../store";
|
import { AppState } from "../store";
|
||||||
|
import { percentage } from "../utils";
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
banner: {
|
banner: {
|
||||||
@ -92,7 +93,9 @@ function QueueInfoBanner(props: Props & ReduxProps) {
|
|||||||
<Typography variant="subtitle2" color="textPrimary" gutterBottom>
|
<Typography variant="subtitle2" color="textPrimary" gutterBottom>
|
||||||
Error rate
|
Error rate
|
||||||
</Typography>
|
</Typography>
|
||||||
<Typography color="textSecondary">0.3 %</Typography>
|
<Typography color="textSecondary">
|
||||||
|
{queue ? percentage(queue.failed, queue.processed) : "-"}
|
||||||
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
@ -79,3 +79,8 @@ export function uuidPrefix(uuid: string): string {
|
|||||||
}
|
}
|
||||||
return uuid.substr(0, idx);
|
return uuid.substr(0, idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function percentage(numerator: number, denominator: number): string {
|
||||||
|
const perc = ((numerator / denominator) * 100).toFixed(2);
|
||||||
|
return `${perc} %`;
|
||||||
|
}
|
||||||
|
@ -1,19 +1,34 @@
|
|||||||
import React from "react";
|
import React, { useEffect } from "react";
|
||||||
|
import { connect, ConnectedProps } from "react-redux";
|
||||||
import { makeStyles } from "@material-ui/core/styles";
|
import { makeStyles } from "@material-ui/core/styles";
|
||||||
import Container from "@material-ui/core/Container";
|
import Container from "@material-ui/core/Container";
|
||||||
import Grid from "@material-ui/core/Grid";
|
import Grid from "@material-ui/core/Grid";
|
||||||
import TasksTable from "../components/TasksTable";
|
import TasksTable from "../components/TasksTable";
|
||||||
import QueueInfoBanner from "../components/QueueInfoBanner";
|
import QueueInfoBanner from "../components/QueueInfoBanner";
|
||||||
|
import QueueBreadCrumb from "../components/QueueBreadcrumb";
|
||||||
import { useParams, useLocation } from "react-router-dom";
|
import { useParams, useLocation } from "react-router-dom";
|
||||||
|
import { listQueuesAsync } from "../actions/queuesActions";
|
||||||
|
import { AppState } from "../store";
|
||||||
|
|
||||||
|
function mapStateToProps(state: AppState) {
|
||||||
|
return {
|
||||||
|
queues: state.queues.data.map((q) => q.name),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const connector = connect(mapStateToProps, { listQueuesAsync });
|
||||||
|
|
||||||
const useStyles = makeStyles((theme) => ({
|
const useStyles = makeStyles((theme) => ({
|
||||||
container: {
|
container: {
|
||||||
paddingTop: theme.spacing(2),
|
paddingTop: theme.spacing(2),
|
||||||
},
|
},
|
||||||
bannerContainer: {
|
breadcrumbs: {
|
||||||
marginBottom: theme.spacing(2),
|
marginBottom: theme.spacing(2),
|
||||||
},
|
},
|
||||||
taskTableContainer: {
|
banner: {
|
||||||
|
marginBottom: theme.spacing(2),
|
||||||
|
},
|
||||||
|
tasksTable: {
|
||||||
marginBottom: theme.spacing(4),
|
marginBottom: theme.spacing(4),
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@ -29,7 +44,7 @@ interface RouteParams {
|
|||||||
const validStatus = ["active", "pending", "scheduled", "retry", "archived"];
|
const validStatus = ["active", "pending", "scheduled", "retry", "archived"];
|
||||||
const defaultStatus = "active";
|
const defaultStatus = "active";
|
||||||
|
|
||||||
function TasksView() {
|
function TasksView(props: ConnectedProps<typeof connector>) {
|
||||||
const classes = useStyles();
|
const classes = useStyles();
|
||||||
const { qname } = useParams<RouteParams>();
|
const { qname } = useParams<RouteParams>();
|
||||||
const query = useQuery();
|
const query = useQuery();
|
||||||
@ -37,14 +52,22 @@ function TasksView() {
|
|||||||
if (!selected || !validStatus.includes(selected)) {
|
if (!selected || !validStatus.includes(selected)) {
|
||||||
selected = defaultStatus;
|
selected = defaultStatus;
|
||||||
}
|
}
|
||||||
|
const { listQueuesAsync } = props;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
listQueuesAsync();
|
||||||
|
}, [listQueuesAsync]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="lg">
|
<Container maxWidth="lg">
|
||||||
<Grid container spacing={0} className={classes.container}>
|
<Grid container spacing={0} className={classes.container}>
|
||||||
<Grid item xs={12} className={classes.bannerContainer}>
|
<Grid xs={12} className={classes.breadcrumbs}>
|
||||||
|
<QueueBreadCrumb queues={props.queues} selectedQueue={qname} />
|
||||||
|
</Grid>
|
||||||
|
<Grid item xs={12} className={classes.banner}>
|
||||||
<QueueInfoBanner qname={qname} />
|
<QueueInfoBanner qname={qname} />
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item xs={12} className={classes.taskTableContainer}>
|
<Grid item xs={12} className={classes.tasksTable}>
|
||||||
<TasksTable queue={qname} selected={selected} />
|
<TasksTable queue={qname} selected={selected} />
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
@ -52,4 +75,4 @@ function TasksView() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default TasksView;
|
export default connector(TasksView);
|
||||||
|
Loading…
Reference in New Issue
Block a user