(ui): Hide action buttons in read-only mode

This commit is contained in:
Ken Hibino 2022-02-26 16:43:58 -08:00
parent 3805ae6e06
commit 49eece97f7
9 changed files with 723 additions and 634 deletions

View File

@ -52,6 +52,7 @@
<script> <script>
window.ROOT_PATH = "%PUBLIC_URL%"; window.ROOT_PATH = "%PUBLIC_URL%";
window.PROMETHEUS_SERVER_ADDRESS = "/[[.PrometheusAddr]]"; window.PROMETHEUS_SERVER_ADDRESS = "/[[.PrometheusAddr]]";
window.READ_ONLY = /[[.ReadOnly]];
</script> </script>
<title>Asynq - Monitoring</title> <title>Asynq - Monitoring</title>
</head> </head>

View File

@ -161,24 +161,26 @@ function ActiveTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Cancel", {
icon: <CancelIcon />, tooltip: "Cancel",
onClick: handleBatchCancelClick, icon: <CancelIcon />,
disabled: props.batchActionPending, onClick: handleBatchCancelClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Cancel All", {
onClick: handleCancelAllClick, label: "Cancel All",
disabled: props.allActionPending, onClick: handleCancelAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -188,30 +190,37 @@ function ActiveTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.key} padding="checkbox"
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }} classes={{ stickyHeader: classes.stickyHeaderCell }}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.key}
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -311,16 +320,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -364,32 +375,34 @@ function Row(props: RowProps) {
<TableCell> <TableCell>
{task.deadline === "-" ? "-" : durationBefore(task.deadline)} {task.deadline === "-" ? "-" : durationBefore(task.deadline)}
</TableCell> </TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
onMouseEnter={props.onActionCellEnter} align="center"
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Cancel"> <React.Fragment>
<IconButton <Tooltip title="Cancel">
onClick={props.onCancelClick} <IconButton
disabled={ onClick={props.onCancelClick}
task.requestPending || task.canceling || task.is_orphaned disabled={
} task.requestPending || task.canceling || task.is_orphaned
size="small" }
> size="small"
<CancelIcon fontSize="small" /> >
</IconButton> <CancelIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

View File

@ -180,35 +180,37 @@ function ArchivedTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Delete", {
icon: <DeleteIcon />, tooltip: "Delete",
onClick: handleBatchDeleteClick, icon: <DeleteIcon />,
disabled: props.batchActionPending, onClick: handleBatchDeleteClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Run", {
icon: <PlayArrowIcon />, tooltip: "Run",
onClick: handleBatchRunClick, icon: <PlayArrowIcon />,
disabled: props.batchActionPending, onClick: handleBatchRunClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Delete All", {
onClick: handleDeleteAllClick, label: "Delete All",
disabled: props.allActionPending, onClick: handleDeleteAllClick,
}, disabled: props.allActionPending,
{ },
label: "Run All", {
onClick: handleRunAllClick, label: "Run All",
disabled: props.allActionPending, onClick: handleRunAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -218,30 +220,37 @@ function ArchivedTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.key} padding="checkbox"
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }} classes={{ stickyHeader: classes.stickyHeaderCell }}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.key}
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -353,16 +362,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -391,42 +402,44 @@ function Row(props: RowProps) {
</TableCell> </TableCell>
<TableCell>{timeAgo(task.last_failed_at)}</TableCell> <TableCell>{timeAgo(task.last_failed_at)}</TableCell>
<TableCell>{task.error_message}</TableCell> <TableCell>{task.error_message}</TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
className={classes.actionCell} align="center"
onMouseEnter={props.onActionCellEnter} className={classes.actionCell}
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Delete"> <React.Fragment>
<IconButton <Tooltip title="Delete">
className={classes.actionButton} <IconButton
onClick={props.onDeleteClick} className={classes.actionButton}
disabled={task.requestPending || props.allActionPending} onClick={props.onDeleteClick}
size="small" disabled={task.requestPending || props.allActionPending}
> size="small"
<DeleteIcon fontSize="small" /> >
</IconButton> <DeleteIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Run"> </Tooltip>
<IconButton <Tooltip title="Run">
className={classes.actionButton} <IconButton
onClick={props.onRunClick} className={classes.actionButton}
disabled={task.requestPending || props.allActionPending} onClick={props.onRunClick}
size="small" disabled={task.requestPending || props.allActionPending}
> size="small"
<PlayArrowIcon fontSize="small" /> >
</IconButton> <PlayArrowIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

View File

@ -167,24 +167,26 @@ function CompletedTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Delete", {
icon: <DeleteIcon />, tooltip: "Delete",
onClick: handleBatchDeleteClick, icon: <DeleteIcon />,
disabled: props.batchActionPending, onClick: handleBatchDeleteClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Delete All", {
onClick: handleDeleteAllClick, label: "Delete All",
disabled: props.allActionPending, onClick: handleDeleteAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -194,30 +196,37 @@ function CompletedTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.key} padding="checkbox"
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }} classes={{ stickyHeader: classes.stickyHeaderCell }}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.key}
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -328,16 +337,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -378,32 +389,34 @@ function Row(props: RowProps) {
? `${stringifyDuration(durationFromSeconds(task.ttl_seconds))} left` ? `${stringifyDuration(durationFromSeconds(task.ttl_seconds))} left`
: `expired`} : `expired`}
</TableCell> </TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
className={classes.actionCell} align="center"
onMouseEnter={props.onActionCellEnter} className={classes.actionCell}
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Delete"> <React.Fragment>
<IconButton <Tooltip title="Delete">
className={classes.actionButton} <IconButton
onClick={props.onDeleteClick} className={classes.actionButton}
disabled={task.requestPending || props.allActionPending} onClick={props.onDeleteClick}
size="small" disabled={task.requestPending || props.allActionPending}
> size="small"
<DeleteIcon fontSize="small" /> >
</IconButton> <DeleteIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

View File

@ -176,35 +176,37 @@ function PendingTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Delete", {
icon: <DeleteIcon />, tooltip: "Delete",
onClick: handleBatchDeleteClick, icon: <DeleteIcon />,
disabled: props.batchActionPending, onClick: handleBatchDeleteClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Archive", {
icon: <ArchiveIcon />, tooltip: "Archive",
onClick: handleBatchArchiveClick, icon: <ArchiveIcon />,
disabled: props.batchActionPending, onClick: handleBatchArchiveClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Delete All", {
onClick: handleDeleteAllClick, label: "Delete All",
disabled: props.allActionPending, onClick: handleDeleteAllClick,
}, disabled: props.allActionPending,
{ },
label: "Archive All", {
onClick: handleArchiveAllClick, label: "Archive All",
disabled: props.allActionPending, onClick: handleArchiveAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -214,32 +216,39 @@ function PendingTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.key} padding="checkbox"
align={col.align} classes={{ stickyHeader: classes.stickyHeaderCell }}
classes={{
stickyHeader: classes.stickyHeaderCell,
}}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.key}
align={col.align}
classes={{
stickyHeader: classes.stickyHeaderCell,
}}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -355,16 +364,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -393,42 +404,44 @@ function Row(props: RowProps) {
</TableCell> </TableCell>
<TableCell align="right">{task.retried}</TableCell> <TableCell align="right">{task.retried}</TableCell>
<TableCell align="right">{task.max_retry}</TableCell> <TableCell align="right">{task.max_retry}</TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
className={classes.actionCell} align="center"
onMouseEnter={props.onActionCellEnter} className={classes.actionCell}
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Delete"> <React.Fragment>
<IconButton <Tooltip title="Delete">
onClick={props.onDeleteClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onDeleteClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<DeleteIcon fontSize="small" /> >
</IconButton> <DeleteIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Archive"> </Tooltip>
<IconButton <Tooltip title="Archive">
onClick={props.onArchiveClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onArchiveClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<ArchiveIcon fontSize="small" /> >
</IconButton> <ArchiveIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

View File

@ -183,25 +183,30 @@ export default function QueuesOverviewTable(props: Props) {
<Table className={classes.table} aria-label="queues overview table"> <Table className={classes.table} aria-label="queues overview table">
<TableHead> <TableHead>
<TableRow> <TableRow>
{colConfigs.map((cfg, i) => ( {colConfigs
<TableCell .filter((cfg) => {
key={cfg.key} // Filter out actions column in readonly mode.
align={cfg.align} return !window.READ_ONLY || cfg.key !== "actions";
className={clsx(i === 0 && classes.fixedCell)} })
> .map((cfg, i) => (
{cfg.sortBy !== SortBy.None ? ( <TableCell
<TableSortLabel key={cfg.key}
active={sortBy === cfg.sortBy} align={cfg.align}
direction={sortDir} className={clsx(i === 0 && classes.fixedCell)}
onClick={createSortClickHandler(cfg.sortBy)} >
> {cfg.sortBy !== SortBy.None ? (
{cfg.label} <TableSortLabel
</TableSortLabel> active={sortBy === cfg.sortBy}
) : ( direction={sortDir}
<div>{cfg.label}</div> onClick={createSortClickHandler(cfg.sortBy)}
)} >
</TableCell> {cfg.label}
))} </TableSortLabel>
) : (
<div>{cfg.label}</div>
)}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -298,50 +303,52 @@ function Row(props: RowProps) {
<TableCell align="right">{q.processed}</TableCell> <TableCell align="right">{q.processed}</TableCell>
<TableCell align="right">{q.failed}</TableCell> <TableCell align="right">{q.failed}</TableCell>
<TableCell align="right">{percentage(q.failed, q.processed)}</TableCell> <TableCell align="right">{percentage(q.failed, q.processed)}</TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
onMouseEnter={() => setShowIcons(true)} align="center"
onMouseLeave={() => setShowIcons(false)} onMouseEnter={() => setShowIcons(true)}
> onMouseLeave={() => setShowIcons(false)}
<div className={classes.actionIconsContainer}> >
{showIcons ? ( <div className={classes.actionIconsContainer}>
<React.Fragment> {showIcons ? (
{q.paused ? ( <React.Fragment>
<Tooltip title="Resume"> {q.paused ? (
<IconButton <Tooltip title="Resume">
color="secondary" <IconButton
onClick={props.onResumeClick} color="secondary"
disabled={q.requestPending} onClick={props.onResumeClick}
size="small" disabled={q.requestPending}
> size="small"
<PlayCircleFilledIcon fontSize="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> </IconButton>
</Tooltip> </Tooltip>
) : ( </React.Fragment>
<Tooltip title="Pause"> ) : (
<IconButton <IconButton size="small">
color="primary" <MoreHorizIcon fontSize="small" />
onClick={props.onPauseClick} </IconButton>
disabled={q.requestPending} )}
size="small" </div>
> </TableCell>
<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> </TableRow>
); );
} }

View File

@ -196,46 +196,48 @@ function RetryTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Delete", {
icon: <DeleteIcon />, tooltip: "Delete",
onClick: handleBatchDeleteClick, icon: <DeleteIcon />,
disabled: props.batchActionPending, onClick: handleBatchDeleteClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Archive", {
icon: <ArchiveIcon />, tooltip: "Archive",
onClick: handleBatchArchiveClick, icon: <ArchiveIcon />,
disabled: props.batchActionPending, onClick: handleBatchArchiveClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Run", {
icon: <PlayArrowIcon />, tooltip: "Run",
onClick: handleBatchRunClick, icon: <PlayArrowIcon />,
disabled: props.batchActionPending, onClick: handleBatchRunClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Delete All", {
onClick: handleDeleteAllClick, label: "Delete All",
disabled: props.allActionPending, onClick: handleDeleteAllClick,
}, disabled: props.allActionPending,
{ },
label: "Archive All", {
onClick: handleArchiveAllClick, label: "Archive All",
disabled: props.allActionPending, onClick: handleArchiveAllClick,
}, disabled: props.allActionPending,
{ },
label: "Run All", {
onClick: handleRunAllClick, label: "Run All",
disabled: props.allActionPending, onClick: handleRunAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -245,30 +247,37 @@ function RetryTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.label} padding="checkbox"
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }} classes={{ stickyHeader: classes.stickyHeaderCell }}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.label}
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -388,16 +397,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -428,52 +439,54 @@ function Row(props: RowProps) {
<TableCell>{task.error_message}</TableCell> <TableCell>{task.error_message}</TableCell>
<TableCell align="right">{task.retried}</TableCell> <TableCell align="right">{task.retried}</TableCell>
<TableCell align="right">{task.max_retry}</TableCell> <TableCell align="right">{task.max_retry}</TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
className={classes.actionCell} align="center"
onMouseEnter={props.onActionCellEnter} className={classes.actionCell}
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Delete"> <React.Fragment>
<IconButton <Tooltip title="Delete">
onClick={props.onDeleteClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onDeleteClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<DeleteIcon fontSize="small" /> >
</IconButton> <DeleteIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Archive"> </Tooltip>
<IconButton <Tooltip title="Archive">
onClick={props.onArchiveClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onArchiveClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<ArchiveIcon fontSize="small" /> >
</IconButton> <ArchiveIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Run"> </Tooltip>
<IconButton <Tooltip title="Run">
onClick={props.onRunClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onRunClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<PlayArrowIcon fontSize="small" /> >
</IconButton> <PlayArrowIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

View File

@ -193,46 +193,48 @@ function ScheduledTasksTable(props: Props & ReduxProps) {
const numSelected = selectedIds.length; const numSelected = selectedIds.length;
return ( return (
<div> <div>
<TableActions {!window.READ_ONLY && (
showIconButtons={numSelected > 0} <TableActions
iconButtonActions={[ showIconButtons={numSelected > 0}
{ iconButtonActions={[
tooltip: "Delete", {
icon: <DeleteIcon />, tooltip: "Delete",
onClick: handleBatchDeleteClick, icon: <DeleteIcon />,
disabled: props.batchActionPending, onClick: handleBatchDeleteClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Archive", {
icon: <ArchiveIcon />, tooltip: "Archive",
onClick: handleBatchArchiveClick, icon: <ArchiveIcon />,
disabled: props.batchActionPending, onClick: handleBatchArchiveClick,
}, disabled: props.batchActionPending,
{ },
tooltip: "Run", {
icon: <PlayArrowIcon />, tooltip: "Run",
onClick: handleBatchRunClick, icon: <PlayArrowIcon />,
disabled: props.batchActionPending, onClick: handleBatchRunClick,
}, disabled: props.batchActionPending,
]} },
menuItemActions={[ ]}
{ menuItemActions={[
label: "Delete All", {
onClick: handleDeleteAllClick, label: "Delete All",
disabled: props.allActionPending, onClick: handleDeleteAllClick,
}, disabled: props.allActionPending,
{ },
label: "Archive All", {
onClick: handleArchiveAllClick, label: "Archive All",
disabled: props.allActionPending, onClick: handleArchiveAllClick,
}, disabled: props.allActionPending,
{ },
label: "Run All", {
onClick: handleRunAllClick, label: "Run All",
disabled: props.allActionPending, onClick: handleRunAllClick,
}, disabled: props.allActionPending,
]} },
/> ]}
/>
)}
<TableContainer component={Paper}> <TableContainer component={Paper}>
<Table <Table
stickyHeader={true} stickyHeader={true}
@ -242,30 +244,37 @@ function ScheduledTasksTable(props: Props & ReduxProps) {
> >
<TableHead> <TableHead>
<TableRow> <TableRow>
<TableCell {!window.READ_ONLY && (
padding="checkbox"
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
<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 <TableCell
key={col.label} padding="checkbox"
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }} classes={{ stickyHeader: classes.stickyHeaderCell }}
> >
{col.label} <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> </TableCell>
))} )}
{columns
.filter((col) => {
// Filter out actions column in readonly mode.
return !window.READ_ONLY || col.key !== "actions";
})
.map((col) => (
<TableCell
key={col.label}
align={col.align}
classes={{ stickyHeader: classes.stickyHeaderCell }}
>
{col.label}
</TableCell>
))}
</TableRow> </TableRow>
</TableHead> </TableHead>
<TableBody> <TableBody>
@ -384,16 +393,18 @@ function Row(props: RowProps) {
selected={props.isSelected} selected={props.isSelected}
onClick={() => history.push(taskDetailsPath(task.queue, task.id))} onClick={() => history.push(taskDetailsPath(task.queue, task.id))}
> >
<TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}> {!window.READ_ONLY && (
<IconButton> <TableCell padding="checkbox" onClick={(e) => e.stopPropagation()}>
<Checkbox <IconButton>
onChange={(event: React.ChangeEvent<HTMLInputElement>) => <Checkbox
props.onSelectChange(event.target.checked) onChange={(event: React.ChangeEvent<HTMLInputElement>) =>
} props.onSelectChange(event.target.checked)
checked={props.isSelected} }
/> checked={props.isSelected}
</IconButton> />
</TableCell> </IconButton>
</TableCell>
)}
<TableCell component="th" scope="row" className={classes.idCell}> <TableCell component="th" scope="row" className={classes.idCell}>
<div className={classes.IdGroup}> <div className={classes.IdGroup}>
{uuidPrefix(task.id)} {uuidPrefix(task.id)}
@ -421,52 +432,54 @@ function Row(props: RowProps) {
</SyntaxHighlighter> </SyntaxHighlighter>
</TableCell> </TableCell>
<TableCell>{durationBefore(task.next_process_at)}</TableCell> <TableCell>{durationBefore(task.next_process_at)}</TableCell>
<TableCell {!window.READ_ONLY && (
align="center" <TableCell
className={classes.actionCell} align="center"
onMouseEnter={props.onActionCellEnter} className={classes.actionCell}
onMouseLeave={props.onActionCellLeave} onMouseEnter={props.onActionCellEnter}
onClick={(e) => e.stopPropagation()} onMouseLeave={props.onActionCellLeave}
> onClick={(e) => e.stopPropagation()}
{props.showActions ? ( >
<React.Fragment> {props.showActions ? (
<Tooltip title="Delete"> <React.Fragment>
<IconButton <Tooltip title="Delete">
onClick={props.onDeleteClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onDeleteClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<DeleteIcon fontSize="small" /> >
</IconButton> <DeleteIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Archive"> </Tooltip>
<IconButton <Tooltip title="Archive">
onClick={props.onArchiveClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onArchiveClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<ArchiveIcon fontSize="small" /> >
</IconButton> <ArchiveIcon fontSize="small" />
</Tooltip> </IconButton>
<Tooltip title="Run"> </Tooltip>
<IconButton <Tooltip title="Run">
onClick={props.onRunClick} <IconButton
disabled={task.requestPending || props.allActionPending} onClick={props.onRunClick}
size="small" disabled={task.requestPending || props.allActionPending}
className={classes.actionButton} size="small"
> className={classes.actionButton}
<PlayArrowIcon fontSize="small" /> >
</IconButton> <PlayArrowIcon fontSize="small" />
</Tooltip> </IconButton>
</React.Fragment> </Tooltip>
) : ( </React.Fragment>
<IconButton size="small" onClick={props.onActionCellEnter}> ) : (
<MoreHorizIcon fontSize="small" /> <IconButton size="small" onClick={props.onActionCellEnter}>
</IconButton> <MoreHorizIcon fontSize="small" />
)} </IconButton>
</TableCell> )}
</TableCell>
)}
</TableRow> </TableRow>
); );
} }

3
ui/src/global.d.ts vendored
View File

@ -6,4 +6,7 @@ interface Window {
// Prometheus server address to query time series data. // Prometheus server address to query time series data.
// This field is set to empty string by default. Use this field only if it's set. // This field is set to empty string by default. Use this field only if it's set.
PROMETHEUS_SERVER_ADDRESS: string; PROMETHEUS_SERVER_ADDRESS: string;
// If true, app hides buttons/links to make non-GET requests to the API server.
READ_ONLY: boolean;
} }