mirror of
https://github.com/hibiken/asynqmon.git
synced 2025-08-24 06:38:42 +08:00
Add Task details view
Allow users to find task by task ID
This commit is contained in:
@@ -33,6 +33,7 @@ import ListItemLink from "./components/ListItemLink";
|
||||
import SchedulersView from "./views/SchedulersView";
|
||||
import DashboardView from "./views/DashboardView";
|
||||
import TasksView from "./views/TasksView";
|
||||
import TaskDetailsView from "./views/TaskDetailsView";
|
||||
import SettingsView from "./views/SettingsView";
|
||||
import ServersView from "./views/ServersView";
|
||||
import RedisInfoView from "./views/RedisInfoView";
|
||||
@@ -269,6 +270,9 @@ function App(props: ConnectedProps<typeof connector>) {
|
||||
<main className={classes.content}>
|
||||
<div className={classes.contentWrapper}>
|
||||
<Switch>
|
||||
<Route exact path={paths.TASK_DETAILS}>
|
||||
<TaskDetailsView />
|
||||
</Route>
|
||||
<Route exact path={paths.QUEUE_DETAILS}>
|
||||
<TasksView />
|
||||
</Route>
|
||||
|
@@ -47,11 +47,16 @@ import {
|
||||
archivePendingTask,
|
||||
batchArchivePendingTasks,
|
||||
archiveAllPendingTasks,
|
||||
TaskInfo,
|
||||
getTaskInfo,
|
||||
} from "../api";
|
||||
import { Dispatch } from "redux";
|
||||
import { toErrorString, toErrorStringWithHttpStatus } from "../utils";
|
||||
|
||||
// List of tasks related action types.
|
||||
export const GET_TASK_INFO_BEGIN = "GET_TASK_INFO_BEGIN";
|
||||
export const GET_TASK_INFO_SUCCESS = "GET_TASK_INFO_SUCCESS";
|
||||
export const GET_TASK_INFO_ERROR = "GET_TASK_INFO_ERROR";
|
||||
export const LIST_ACTIVE_TASKS_BEGIN = "LIST_ACTIVE_TASKS_BEGIN";
|
||||
export const LIST_ACTIVE_TASKS_SUCCESS = "LIST_ACTIVE_TASKS_SUCCESS";
|
||||
export const LIST_ACTIVE_TASKS_ERROR = "LIST_ACTIVE_TASKS_ERROR";
|
||||
@@ -80,9 +85,9 @@ export const BATCH_CANCEL_ACTIVE_TASKS_SUCCESS =
|
||||
"BATCH_CANCEL_ACTIVE_TASKS_SUCCESS";
|
||||
export const BATCH_CANCEL_ACTIVE_TASKS_ERROR =
|
||||
"BATCH_CANCEL_ACTIVE_TASKS_ERROR";
|
||||
export const RUN_SCHEDULED_TASK_BEGIN = "RUN_ARCHIVED_TASK_BEGIN";
|
||||
export const RUN_SCHEDULED_TASK_SUCCESS = "RUN_ARCHIVED_TASK_SUCCESS";
|
||||
export const RUN_SCHEDULED_TASK_ERROR = "RUN_ARCHIVED_TASK_ERROR";
|
||||
export const RUN_SCHEDULED_TASK_BEGIN = "RUN_SCHEDULED_TASK_BEGIN";
|
||||
export const RUN_SCHEDULED_TASK_SUCCESS = "RUN_SCHEDULED_TASK_SUCCESS";
|
||||
export const RUN_SCHEDULED_TASK_ERROR = "RUN_SCHEDULED_TASK_ERROR";
|
||||
export const RUN_RETRY_TASK_BEGIN = "RUN_RETRY_TASK_BEGIN";
|
||||
export const RUN_RETRY_TASK_SUCCESS = "RUN_RETRY_TASK_SUCCESS";
|
||||
export const RUN_RETRY_TASK_ERROR = "RUN_RETRY_TASK_ERROR";
|
||||
@@ -209,6 +214,20 @@ export const DELETE_ALL_ARCHIVED_TASKS_SUCCESS =
|
||||
export const DELETE_ALL_ARCHIVED_TASKS_ERROR =
|
||||
"DELETE_ALL_ARCHIVED_TASKS_ERROR";
|
||||
|
||||
interface GetTaskInfoBeginAction {
|
||||
type: typeof GET_TASK_INFO_BEGIN;
|
||||
}
|
||||
|
||||
interface GetTaskInfoErrorAction {
|
||||
type: typeof GET_TASK_INFO_ERROR;
|
||||
error: string; // error description
|
||||
}
|
||||
|
||||
interface GetTaskInfoSuccessAction {
|
||||
type: typeof GET_TASK_INFO_SUCCESS;
|
||||
payload: TaskInfo;
|
||||
}
|
||||
|
||||
interface ListActiveTasksBeginAction {
|
||||
type: typeof LIST_ACTIVE_TASKS_BEGIN;
|
||||
queue: string;
|
||||
@@ -894,6 +913,9 @@ interface DeleteAllArchivedTasksErrorAction {
|
||||
|
||||
// Union of all tasks related action types.
|
||||
export type TasksActionTypes =
|
||||
| GetTaskInfoBeginAction
|
||||
| GetTaskInfoErrorAction
|
||||
| GetTaskInfoSuccessAction
|
||||
| ListActiveTasksBeginAction
|
||||
| ListActiveTasksSuccessAction
|
||||
| ListActiveTasksErrorAction
|
||||
@@ -1009,6 +1031,25 @@ export type TasksActionTypes =
|
||||
| DeleteAllArchivedTasksSuccessAction
|
||||
| DeleteAllArchivedTasksErrorAction;
|
||||
|
||||
export function getTaskInfoAsync(qname: string, id: string) {
|
||||
return async (dispatch: Dispatch<TasksActionTypes>) => {
|
||||
dispatch({ type: GET_TASK_INFO_BEGIN });
|
||||
try {
|
||||
const response = await getTaskInfo(qname, id);
|
||||
dispatch({
|
||||
type: GET_TASK_INFO_SUCCESS,
|
||||
payload: response,
|
||||
})
|
||||
} catch (error) {
|
||||
console.error("getTaskInfoAsync: ", toErrorStringWithHttpStatus(error));
|
||||
dispatch({
|
||||
type: GET_TASK_INFO_ERROR,
|
||||
error: toErrorString(error),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function listActiveTasksAsync(
|
||||
qname: string,
|
||||
pageOpts?: PaginationOptions
|
||||
|
@@ -245,6 +245,21 @@ interface BaseTask {
|
||||
payload: string;
|
||||
}
|
||||
|
||||
export interface TaskInfo {
|
||||
id: string;
|
||||
queue: string;
|
||||
type: string;
|
||||
payload: string;
|
||||
state: string;
|
||||
max_retry: number;
|
||||
retried: number;
|
||||
last_failed_at: string;
|
||||
error_message: string;
|
||||
next_process_at: string;
|
||||
timeout_seconds: number;
|
||||
deadline: string;
|
||||
}
|
||||
|
||||
export interface ActiveTask extends BaseTask {
|
||||
id: string;
|
||||
queue: string;
|
||||
@@ -369,6 +384,15 @@ export async function listQueueStats(): Promise<ListQueueStatsResponse> {
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
export async function getTaskInfo(qname: string, id: string): Promise<TaskInfo> {
|
||||
const url = `${BASE_URL}/queues/${qname}/tasks/${id}`;
|
||||
const resp = await axios({
|
||||
method: "get",
|
||||
url,
|
||||
});
|
||||
return resp.data;
|
||||
}
|
||||
|
||||
export async function listActiveTasks(
|
||||
qname: string,
|
||||
pageOpts?: PaginationOptions
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -34,6 +35,7 @@ import { usePolling } from "../hooks";
|
||||
import { ActiveTaskExtended } from "../reducers/tasksReducer";
|
||||
import { durationBefore, timeAgo, uuidPrefix } from "../utils";
|
||||
import { TableColumn } from "../types/table";
|
||||
import { taskDetailsPath } from "../paths";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
table: {
|
||||
@@ -189,14 +191,16 @@ function ActiveTasksTable(props: Props & ReduxProps) {
|
||||
padding="checkbox"
|
||||
classes={{ stickyHeader: classes.stickyHeaderCell }}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{columns.map((col) => (
|
||||
<TableCell
|
||||
@@ -257,6 +261,18 @@ function ActiveTasksTable(props: Props & ReduxProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const useRowStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:hover .MuiTableCell-root": {
|
||||
borderBottomColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
interface RowProps {
|
||||
task: ActiveTaskExtended;
|
||||
isSelected: boolean;
|
||||
@@ -269,15 +285,24 @@ interface RowProps {
|
||||
|
||||
function Row(props: RowProps) {
|
||||
const { task } = props;
|
||||
const classes = useRowStyles();
|
||||
const history = useHistory();
|
||||
return (
|
||||
<TableRow key={task.id} selected={props.isSelected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
<TableRow
|
||||
key={task.id}
|
||||
className={classes.root}
|
||||
selected={props.isSelected}
|
||||
onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
|
||||
>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{uuidPrefix(task.id)}
|
||||
@@ -302,6 +327,7 @@ function Row(props: RowProps) {
|
||||
align="center"
|
||||
onMouseEnter={props.onActionCellEnter}
|
||||
onMouseLeave={props.onActionCellLeave}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.showActions ? (
|
||||
<React.Fragment>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -38,6 +39,7 @@ import { timeAgo, uuidPrefix } from "../utils";
|
||||
import { usePolling } from "../hooks";
|
||||
import { ArchivedTaskExtended } from "../reducers/tasksReducer";
|
||||
import { TableColumn } from "../types/table";
|
||||
import { taskDetailsPath } from "../paths";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
table: {
|
||||
@@ -216,14 +218,16 @@ function ArchivedTasksTable(props: Props & ReduxProps) {
|
||||
padding="checkbox"
|
||||
classes={{ stickyHeader: classes.stickyHeaderCell }}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{columns.map((col) => (
|
||||
<TableCell
|
||||
@@ -288,6 +292,15 @@ function ArchivedTasksTable(props: Props & ReduxProps) {
|
||||
}
|
||||
|
||||
const useRowStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:hover .MuiTableCell-root": {
|
||||
borderBottomColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
actionCell: {
|
||||
width: "96px",
|
||||
},
|
||||
@@ -312,15 +325,23 @@ interface RowProps {
|
||||
function Row(props: RowProps) {
|
||||
const { task } = props;
|
||||
const classes = useRowStyles();
|
||||
const history = useHistory();
|
||||
return (
|
||||
<TableRow key={task.id} selected={props.isSelected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
<TableRow
|
||||
key={task.id}
|
||||
className={classes.root}
|
||||
selected={props.isSelected}
|
||||
onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
|
||||
>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{uuidPrefix(task.id)}
|
||||
@@ -341,6 +362,7 @@ function Row(props: RowProps) {
|
||||
className={classes.actionCell}
|
||||
onMouseEnter={props.onActionCellEnter}
|
||||
onMouseLeave={props.onActionCellLeave}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.showActions ? (
|
||||
<React.Fragment>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -38,6 +39,7 @@ import { usePolling } from "../hooks";
|
||||
import { uuidPrefix } from "../utils";
|
||||
import { TableColumn } from "../types/table";
|
||||
import { PendingTaskExtended } from "../reducers/tasksReducer";
|
||||
import { taskDetailsPath } from "../paths";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
table: {
|
||||
@@ -216,14 +218,16 @@ function PendingTasksTable(props: Props & ReduxProps) {
|
||||
padding="checkbox"
|
||||
classes={{ stickyHeader: classes.stickyHeaderCell }}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{columns.map((col) => (
|
||||
<TableCell
|
||||
@@ -289,7 +293,16 @@ function PendingTasksTable(props: Props & ReduxProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
const useRowStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:hover .MuiTableCell-root": {
|
||||
borderBottomColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
actionCell: {
|
||||
width: "96px",
|
||||
},
|
||||
@@ -297,7 +310,7 @@ const useRowStyles = makeStyles({
|
||||
marginLeft: 3,
|
||||
marginRight: 3,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
interface RowProps {
|
||||
task: PendingTaskExtended;
|
||||
@@ -314,15 +327,23 @@ interface RowProps {
|
||||
function Row(props: RowProps) {
|
||||
const { task } = props;
|
||||
const classes = useRowStyles();
|
||||
const history = useHistory();
|
||||
return (
|
||||
<TableRow key={task.id} selected={props.isSelected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
<TableRow
|
||||
key={task.id}
|
||||
className={classes.root}
|
||||
selected={props.isSelected}
|
||||
onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
|
||||
>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{uuidPrefix(task.id)}
|
||||
@@ -343,6 +364,7 @@ function Row(props: RowProps) {
|
||||
className={classes.actionCell}
|
||||
onMouseEnter={props.onActionCellEnter}
|
||||
onMouseLeave={props.onActionCellLeave}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.showActions ? (
|
||||
<React.Fragment>
|
||||
|
@@ -31,7 +31,9 @@ interface Props {
|
||||
// All queue names.
|
||||
queues: string[];
|
||||
// Name of the queue currently selected.
|
||||
selectedQueue: string;
|
||||
queueName: string;
|
||||
// ID of the task currently selected (optional).
|
||||
taskId?: string;
|
||||
}
|
||||
|
||||
export default function QueueBreadcrumbs(props: Props) {
|
||||
@@ -57,11 +59,12 @@ export default function QueueBreadcrumbs(props: Props) {
|
||||
onClick={() => history.push(paths.HOME)}
|
||||
/>
|
||||
<StyledBreadcrumb
|
||||
label={props.selectedQueue}
|
||||
label={props.queueName}
|
||||
deleteIcon={<ExpandMoreIcon />}
|
||||
onClick={handleClick}
|
||||
onDelete={handleClick}
|
||||
/>
|
||||
{props.taskId && <StyledBreadcrumb label={`task:${props.taskId}`} />}
|
||||
</Breadcrumbs>
|
||||
<Menu
|
||||
id="queue-breadcrumb-menu"
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useCallback, useState } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -42,6 +43,7 @@ import { durationBefore, uuidPrefix } from "../utils";
|
||||
import { usePolling } from "../hooks";
|
||||
import { RetryTaskExtended } from "../reducers/tasksReducer";
|
||||
import { TableColumn } from "../types/table";
|
||||
import { taskDetailsPath } from "../paths";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
table: {
|
||||
@@ -246,14 +248,16 @@ function RetryTasksTable(props: Props & ReduxProps) {
|
||||
padding="checkbox"
|
||||
classes={{ stickyHeader: classes.stickyHeaderCell }}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{columns.map((col) => (
|
||||
<TableCell
|
||||
@@ -320,7 +324,16 @@ function RetryTasksTable(props: Props & ReduxProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
const useRowStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:hover .MuiTableCell-root": {
|
||||
borderBottomColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
actionCell: {
|
||||
width: "140px",
|
||||
},
|
||||
@@ -328,7 +341,7 @@ const useRowStyles = makeStyles({
|
||||
marginLeft: 3,
|
||||
marginRight: 3,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
interface RowProps {
|
||||
task: RetryTaskExtended;
|
||||
@@ -346,15 +359,24 @@ interface RowProps {
|
||||
function Row(props: RowProps) {
|
||||
const { task } = props;
|
||||
const classes = useRowStyles();
|
||||
const history = useHistory();
|
||||
|
||||
return (
|
||||
<TableRow key={task.id} selected={props.isSelected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
<TableRow
|
||||
key={task.id}
|
||||
className={classes.root}
|
||||
selected={props.isSelected}
|
||||
onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
|
||||
>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{uuidPrefix(task.id)}
|
||||
@@ -377,6 +399,7 @@ function Row(props: RowProps) {
|
||||
className={classes.actionCell}
|
||||
onMouseEnter={props.onActionCellEnter}
|
||||
onMouseLeave={props.onActionCellLeave}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.showActions ? (
|
||||
<React.Fragment>
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import React, { useState, useCallback } from "react";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Table from "@material-ui/core/Table";
|
||||
@@ -42,6 +43,7 @@ import { durationBefore, uuidPrefix } from "../utils";
|
||||
import { usePolling } from "../hooks";
|
||||
import { ScheduledTaskExtended } from "../reducers/tasksReducer";
|
||||
import { TableColumn } from "../types/table";
|
||||
import { taskDetailsPath } from "../paths";
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
table: {
|
||||
@@ -243,14 +245,16 @@ function ScheduledTasksTable(props: Props & ReduxProps) {
|
||||
padding="checkbox"
|
||||
classes={{ stickyHeader: classes.stickyHeaderCell }}
|
||||
>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
indeterminate={numSelected > 0 && numSelected < rowCount}
|
||||
checked={rowCount > 0 && numSelected === rowCount}
|
||||
onChange={handleSelectAllClick}
|
||||
inputProps={{
|
||||
"aria-label": "select all tasks shown in the table",
|
||||
}}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
{columns.map((col) => (
|
||||
<TableCell
|
||||
@@ -317,7 +321,16 @@ function ScheduledTasksTable(props: Props & ReduxProps) {
|
||||
);
|
||||
}
|
||||
|
||||
const useRowStyles = makeStyles({
|
||||
const useRowStyles = makeStyles((theme) => ({
|
||||
root: {
|
||||
cursor: "pointer",
|
||||
"&:hover": {
|
||||
boxShadow: theme.shadows[2],
|
||||
},
|
||||
"&:hover .MuiTableCell-root": {
|
||||
borderBottomColor: theme.palette.background.paper,
|
||||
},
|
||||
},
|
||||
actionCell: {
|
||||
width: "140px",
|
||||
},
|
||||
@@ -325,7 +338,7 @@ const useRowStyles = makeStyles({
|
||||
marginLeft: 3,
|
||||
marginRight: 3,
|
||||
},
|
||||
});
|
||||
}));
|
||||
|
||||
interface RowProps {
|
||||
task: ScheduledTaskExtended;
|
||||
@@ -343,15 +356,23 @@ interface RowProps {
|
||||
function Row(props: RowProps) {
|
||||
const { task } = props;
|
||||
const classes = useRowStyles();
|
||||
const history = useHistory();
|
||||
return (
|
||||
<TableRow key={task.id} selected={props.isSelected}>
|
||||
<TableCell padding="checkbox">
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
<TableRow
|
||||
key={task.id}
|
||||
className={classes.root}
|
||||
selected={props.isSelected}
|
||||
onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
|
||||
>
|
||||
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
|
||||
<IconButton>
|
||||
<Checkbox
|
||||
onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
|
||||
props.onSelectChange(event.target.checked)
|
||||
}
|
||||
checked={props.isSelected}
|
||||
/>
|
||||
</IconButton>
|
||||
</TableCell>
|
||||
<TableCell component="th" scope="row">
|
||||
{uuidPrefix(task.id)}
|
||||
@@ -371,6 +392,7 @@ function Row(props: RowProps) {
|
||||
className={classes.actionCell}
|
||||
onMouseEnter={props.onActionCellEnter}
|
||||
onMouseLeave={props.onActionCellLeave}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
{props.showActions ? (
|
||||
<React.Fragment>
|
||||
|
@@ -1,16 +1,18 @@
|
||||
import React from "react";
|
||||
import React, { useState } from "react";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Chip from "@material-ui/core/Chip";
|
||||
import InputBase from "@material-ui/core/InputBase";
|
||||
import SearchIcon from "@material-ui/icons/Search";
|
||||
import ActiveTasksTable from "./ActiveTasksTable";
|
||||
import PendingTasksTable from "./PendingTasksTable";
|
||||
import ScheduledTasksTable from "./ScheduledTasksTable";
|
||||
import RetryTasksTable from "./RetryTasksTable";
|
||||
import ArchivedTasksTable from "./ArchivedTasksTable";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { queueDetailsPath } from "../paths";
|
||||
import { queueDetailsPath, taskDetailsPath } from "../paths";
|
||||
import { QueueInfo } from "../reducers/queuesReducer";
|
||||
import { AppState } from "../store";
|
||||
import { isDarkTheme } from "../theme";
|
||||
@@ -101,6 +103,38 @@ const useStyles = makeStyles((theme) => ({
|
||||
borderRadius: "10px",
|
||||
marginLeft: "2px",
|
||||
},
|
||||
searchbar: {
|
||||
marginLeft: theme.spacing(4),
|
||||
},
|
||||
search: {
|
||||
position: "relative",
|
||||
width: "312px",
|
||||
borderRadius: "18px",
|
||||
backgroundColor: isDarkTheme(theme) ? "#303030" : theme.palette.grey[100],
|
||||
"&:hover, &:focus": {
|
||||
backgroundColor: isDarkTheme(theme) ? "#303030" : theme.palette.grey[200],
|
||||
},
|
||||
},
|
||||
searchIcon: {
|
||||
padding: theme.spacing(0, 2),
|
||||
height: "100%",
|
||||
position: "absolute",
|
||||
pointerEvents: "none",
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
},
|
||||
inputRoot: {
|
||||
color: "inherit",
|
||||
width: "100%",
|
||||
},
|
||||
inputInput: {
|
||||
padding: theme.spacing(1, 1, 1, 0),
|
||||
// vertical padding + font size from searchIcon
|
||||
paddingLeft: `calc(1em + ${theme.spacing(4)}px)`,
|
||||
width: "100%",
|
||||
fontSize: "0.85rem",
|
||||
},
|
||||
}));
|
||||
|
||||
function TasksTable(props: Props & ReduxProps) {
|
||||
@@ -115,6 +149,8 @@ function TasksTable(props: Props & ReduxProps) {
|
||||
{ key: "archived", label: "Archived", count: currentStats.archived },
|
||||
];
|
||||
|
||||
const [searchQuery, setSearchQuery] = useState<string>("");
|
||||
|
||||
return (
|
||||
<Paper variant="outlined" className={classes.container}>
|
||||
<div className={classes.header}>
|
||||
@@ -137,6 +173,34 @@ function TasksTable(props: Props & ReduxProps) {
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<div className={classes.searchbar}>
|
||||
<div className={classes.search}>
|
||||
<div className={classes.searchIcon}>
|
||||
<SearchIcon />
|
||||
</div>
|
||||
<InputBase
|
||||
placeholder="Search by ID"
|
||||
classes={{
|
||||
root: classes.inputRoot,
|
||||
input: classes.inputInput,
|
||||
}}
|
||||
value={searchQuery}
|
||||
onChange={(e) => {
|
||||
setSearchQuery(e.target.value);
|
||||
}}
|
||||
inputProps={{
|
||||
"aria-label": "search",
|
||||
onKeyDown: (e) => {
|
||||
if (e.key === "Enter") {
|
||||
history.push(
|
||||
taskDetailsPath(props.queue, searchQuery.trim())
|
||||
);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<TabPanel value="active" selected={props.selected}>
|
||||
<ActiveTasksTable queue={props.queue} />
|
||||
|
@@ -5,8 +5,13 @@ export const paths = {
|
||||
SCHEDULERS: "/schedulers",
|
||||
QUEUE_DETAILS: "/queues/:qname",
|
||||
REDIS: "/redis",
|
||||
TASK_DETAILS: "/queues/:qname/tasks/:taskId",
|
||||
};
|
||||
|
||||
/**************************************************************
|
||||
Path Helper functions
|
||||
**************************************************************/
|
||||
|
||||
export function queueDetailsPath(qname: string, taskStatus?: string): string {
|
||||
const path = paths.QUEUE_DETAILS.replace(":qname", qname);
|
||||
if (taskStatus) {
|
||||
@@ -14,3 +19,20 @@ export function queueDetailsPath(qname: string, taskStatus?: string): string {
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
export function taskDetailsPath(qname: string, taskId: string): string {
|
||||
return paths.TASK_DETAILS.replace(":qname", qname).replace(":taskId", taskId);
|
||||
}
|
||||
|
||||
/**************************************************************
|
||||
URL Params
|
||||
**************************************************************/
|
||||
|
||||
export interface QueueDetailsRouteParams {
|
||||
qname: string;
|
||||
}
|
||||
|
||||
export interface TaskDetailsRouteParams {
|
||||
qname: string;
|
||||
taskId: string;
|
||||
}
|
@@ -114,6 +114,9 @@ import {
|
||||
BATCH_DELETE_PENDING_TASKS_SUCCESS,
|
||||
BATCH_ARCHIVE_PENDING_TASKS_ERROR,
|
||||
BATCH_DELETE_PENDING_TASKS_ERROR,
|
||||
GET_TASK_INFO_BEGIN,
|
||||
GET_TASK_INFO_ERROR,
|
||||
GET_TASK_INFO_SUCCESS,
|
||||
} from "../actions/tasksActions";
|
||||
import {
|
||||
ActiveTask,
|
||||
@@ -121,6 +124,7 @@ import {
|
||||
PendingTask,
|
||||
RetryTask,
|
||||
ScheduledTask,
|
||||
TaskInfo,
|
||||
} from "../api";
|
||||
|
||||
export interface ActiveTaskExtended extends ActiveTask {
|
||||
@@ -193,6 +197,11 @@ interface TasksState {
|
||||
error: string;
|
||||
data: ArchivedTaskExtended[];
|
||||
};
|
||||
taskInfo: {
|
||||
loading: boolean;
|
||||
error: string;
|
||||
data?: TaskInfo;
|
||||
},
|
||||
}
|
||||
|
||||
const initialState: TasksState = {
|
||||
@@ -231,6 +240,10 @@ const initialState: TasksState = {
|
||||
error: "",
|
||||
data: [],
|
||||
},
|
||||
taskInfo: {
|
||||
loading: false,
|
||||
error: "",
|
||||
}
|
||||
};
|
||||
|
||||
function tasksReducer(
|
||||
@@ -238,6 +251,34 @@ function tasksReducer(
|
||||
action: TasksActionTypes
|
||||
): TasksState {
|
||||
switch (action.type) {
|
||||
case GET_TASK_INFO_BEGIN:
|
||||
return {
|
||||
...state,
|
||||
taskInfo: {
|
||||
...state.taskInfo,
|
||||
loading: true,
|
||||
},
|
||||
}
|
||||
|
||||
case GET_TASK_INFO_ERROR:
|
||||
return {
|
||||
...state,
|
||||
taskInfo: {
|
||||
loading: false,
|
||||
error: action.error,
|
||||
},
|
||||
};
|
||||
|
||||
case GET_TASK_INFO_SUCCESS:
|
||||
return {
|
||||
...state,
|
||||
taskInfo: {
|
||||
loading: false,
|
||||
error: "",
|
||||
data: action.payload,
|
||||
},
|
||||
};
|
||||
|
||||
case LIST_ACTIVE_TASKS_BEGIN:
|
||||
return {
|
||||
...state,
|
||||
|
251
ui/src/views/TaskDetailsView.tsx
Normal file
251
ui/src/views/TaskDetailsView.tsx
Normal file
@@ -0,0 +1,251 @@
|
||||
import React, { useMemo, useEffect } from "react";
|
||||
import { connect, ConnectedProps } from "react-redux";
|
||||
import { useHistory } from "react-router-dom";
|
||||
import { makeStyles } from "@material-ui/core/styles";
|
||||
import Container from "@material-ui/core/Container";
|
||||
import Grid from "@material-ui/core/Grid";
|
||||
import Paper from "@material-ui/core/Paper";
|
||||
import Typography from "@material-ui/core/Typography";
|
||||
import Button from "@material-ui/core/Button";
|
||||
import Alert from "@material-ui/lab/Alert";
|
||||
import AlertTitle from "@material-ui/lab/AlertTitle";
|
||||
import ArrowBackIcon from "@material-ui/icons/ArrowBack";
|
||||
import { useParams } from "react-router-dom";
|
||||
import QueueBreadCrumb from "../components/QueueBreadcrumb";
|
||||
import { AppState } from "../store";
|
||||
import { getTaskInfoAsync } from "../actions/tasksActions";
|
||||
import { TaskDetailsRouteParams } from "../paths";
|
||||
import { usePolling } from "../hooks";
|
||||
import { listQueuesAsync } from "../actions/queuesActions";
|
||||
import SyntaxHighlighter from "../components/SyntaxHighlighter";
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
loading: state.tasks.taskInfo.loading,
|
||||
error: state.tasks.taskInfo.error,
|
||||
taskInfo: state.tasks.taskInfo.data,
|
||||
pollInterval: state.settings.pollInterval,
|
||||
queues: state.queues.data.map((q) => q.name), // FIXME: This data may not be available
|
||||
};
|
||||
}
|
||||
|
||||
const connector = connect(mapStateToProps, {
|
||||
getTaskInfoAsync,
|
||||
listQueuesAsync,
|
||||
});
|
||||
|
||||
const useStyles = makeStyles((theme) => ({
|
||||
container: {
|
||||
paddingTop: theme.spacing(2),
|
||||
},
|
||||
alert: {
|
||||
borderTopLeftRadius: 0,
|
||||
borderTopRightRadius: 0,
|
||||
},
|
||||
paper: {
|
||||
padding: theme.spacing(2),
|
||||
marginTop: theme.spacing(2),
|
||||
},
|
||||
breadcrumbs: {
|
||||
marginBottom: theme.spacing(2),
|
||||
},
|
||||
infoRow: {
|
||||
display: "flex",
|
||||
alignItems: "center",
|
||||
paddingTop: theme.spacing(1),
|
||||
},
|
||||
infoKeyCell: {
|
||||
width: "140px",
|
||||
},
|
||||
infoValueCell: {
|
||||
width: "auto",
|
||||
},
|
||||
footer: {
|
||||
paddingTop: theme.spacing(3),
|
||||
paddingBottom: theme.spacing(3),
|
||||
},
|
||||
}));
|
||||
|
||||
type Props = ConnectedProps<typeof connector>;
|
||||
|
||||
function TaskDetailsView(props: Props) {
|
||||
const classes = useStyles();
|
||||
const { qname, taskId } = useParams<TaskDetailsRouteParams>();
|
||||
const { getTaskInfoAsync, pollInterval, listQueuesAsync, taskInfo } = props;
|
||||
const history = useHistory();
|
||||
|
||||
const fetchTaskInfo = useMemo(() => {
|
||||
return () => {
|
||||
getTaskInfoAsync(qname, taskId);
|
||||
};
|
||||
}, [qname, taskId, getTaskInfoAsync]);
|
||||
|
||||
usePolling(fetchTaskInfo, pollInterval);
|
||||
|
||||
// Fetch queues data to populate props.queues
|
||||
useEffect(() => {
|
||||
listQueuesAsync();
|
||||
}, [listQueuesAsync]);
|
||||
|
||||
return (
|
||||
<Container maxWidth="lg" className={classes.container}>
|
||||
<Grid container spacing={0}>
|
||||
<Grid item xs={12} className={classes.breadcrumbs}>
|
||||
<QueueBreadCrumb
|
||||
queues={props.queues}
|
||||
queueName={qname}
|
||||
taskId={taskId}
|
||||
/>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
{props.error ? (
|
||||
<Alert severity="error" className={classes.alert}>
|
||||
<AlertTitle>Error</AlertTitle>
|
||||
{props.error}
|
||||
</Alert>
|
||||
) : (
|
||||
<Paper className={classes.paper} variant="outlined">
|
||||
<Typography variant="h6">Task Info</Typography>
|
||||
<div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
ID:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.id}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
Type:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.type}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
State:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.state}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
Queue:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.queue}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
Retry:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.retried}/{taskInfo?.max_retry}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
Last Failure:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.last_failed_at ? (
|
||||
<Typography>
|
||||
{taskInfo?.error_message} ({taskInfo?.last_failed_at})
|
||||
</Typography>
|
||||
) : (
|
||||
<Typography>n/a</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography
|
||||
variant="subtitle2"
|
||||
className={classes.infoKeyCell}
|
||||
>
|
||||
Next Process Time:{" "}
|
||||
</Typography>
|
||||
{taskInfo?.next_process_at ? (
|
||||
<Typography>{taskInfo?.next_process_at}</Typography>
|
||||
) : (
|
||||
<Typography>n/a</Typography>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography variant="subtitle2" className={classes.infoKeyCell}>
|
||||
Timeout:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.timeout_seconds ? (
|
||||
<Typography>{taskInfo?.timeout_seconds} seconds</Typography>
|
||||
) : (
|
||||
<Typography>n/a</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography variant="subtitle2" className={classes.infoKeyCell}>
|
||||
Deadline:{" "}
|
||||
</Typography>
|
||||
<Typography className={classes.infoValueCell}>
|
||||
{taskInfo?.deadline ? (
|
||||
<Typography>{taskInfo?.deadline}</Typography>
|
||||
) : (
|
||||
<Typography>n/a</Typography>
|
||||
)}
|
||||
</Typography>
|
||||
</div>
|
||||
<div className={classes.infoRow}>
|
||||
<Typography variant="subtitle2" className={classes.infoKeyCell}>
|
||||
Payload:{" "}
|
||||
</Typography>
|
||||
<div className={classes.infoValueCell}>
|
||||
{taskInfo?.payload && (
|
||||
<SyntaxHighlighter
|
||||
language="json"
|
||||
customStyle={{ margin: 0, maxWidth: 400 }}
|
||||
>
|
||||
{taskInfo.payload}
|
||||
</SyntaxHighlighter>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</Paper>
|
||||
)}
|
||||
<div className={classes.footer}>
|
||||
<Button
|
||||
startIcon={<ArrowBackIcon />}
|
||||
onClick={() => history.goBack()}
|
||||
>
|
||||
Go Back
|
||||
</Button>
|
||||
</div>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default connector(TaskDetailsView);
|
@@ -9,6 +9,7 @@ import QueueBreadCrumb from "../components/QueueBreadcrumb";
|
||||
import { useParams, useLocation } from "react-router-dom";
|
||||
import { listQueuesAsync } from "../actions/queuesActions";
|
||||
import { AppState } from "../store";
|
||||
import { QueueDetailsRouteParams } from "../paths";
|
||||
|
||||
function mapStateToProps(state: AppState) {
|
||||
return {
|
||||
@@ -37,16 +38,12 @@ function useQuery(): URLSearchParams {
|
||||
return new URLSearchParams(useLocation().search);
|
||||
}
|
||||
|
||||
interface RouteParams {
|
||||
qname: string;
|
||||
}
|
||||
|
||||
const validStatus = ["active", "pending", "scheduled", "retry", "archived"];
|
||||
const defaultStatus = "active";
|
||||
|
||||
function TasksView(props: ConnectedProps<typeof connector>) {
|
||||
const classes = useStyles();
|
||||
const { qname } = useParams<RouteParams>();
|
||||
const { qname } = useParams<QueueDetailsRouteParams>();
|
||||
const query = useQuery();
|
||||
let selected = query.get("status");
|
||||
if (!selected || !validStatus.includes(selected)) {
|
||||
@@ -62,7 +59,7 @@ function TasksView(props: ConnectedProps<typeof connector>) {
|
||||
<Container maxWidth="lg">
|
||||
<Grid container spacing={0} className={classes.container}>
|
||||
<Grid item xs={12} className={classes.breadcrumbs}>
|
||||
<QueueBreadCrumb queues={props.queues} selectedQueue={qname} />
|
||||
<QueueBreadCrumb queues={props.queues} queueName={qname} />
|
||||
</Grid>
|
||||
<Grid item xs={12} className={classes.banner}>
|
||||
<QueueInfoBanner qname={qname} />
|
||||
|
Reference in New Issue
Block a user