diff --git a/ui/src/App.tsx b/ui/src/App.tsx index 8e44bf2..bbacd44 100644 --- a/ui/src/App.tsx +++ b/ui/src/App.tsx @@ -14,7 +14,7 @@ import LayersIcon from "@material-ui/icons/Layers"; import SettingsIcon from "@material-ui/icons/Settings"; import { paths } from "./paths"; import ListItemLink from "./components/ListItemLink"; -import CronView from "./views/CronView"; +import SchedulersView from "./views/SchedulersView"; import DashboardView from "./views/DashboardView"; import TasksView from "./views/TasksView"; import SettingsView from "./views/SettingsView"; @@ -147,20 +147,20 @@ function App() {
} /> } />
} /> @@ -173,8 +173,8 @@ function App() { - - + + diff --git a/ui/src/components/CronEntriesTable.tsx b/ui/src/components/SchedulerEntriesTable.tsx similarity index 72% rename from ui/src/components/CronEntriesTable.tsx rename to ui/src/components/SchedulerEntriesTable.tsx index 8953da4..a216d6d 100644 --- a/ui/src/components/CronEntriesTable.tsx +++ b/ui/src/components/SchedulerEntriesTable.tsx @@ -7,10 +7,14 @@ import TableCell from "@material-ui/core/TableCell"; import TableContainer from "@material-ui/core/TableContainer"; import TableHead from "@material-ui/core/TableHead"; import TableRow from "@material-ui/core/TableRow"; +import Alert from "@material-ui/lab/Alert"; +import AlertTitle from "@material-ui/lab/AlertTitle"; import SyntaxHighlighter from "react-syntax-highlighter"; import syntaxHighlightStyle from "react-syntax-highlighter/dist/esm/styles/hljs/github"; import { SortDirection, ColumnConfig } from "../types/table"; import TableSortLabel from "@material-ui/core/TableSortLabel"; +import { SchedulerEntry } from "../api"; +import { timeAgo, durationBefore } from "../timeutil"; const useStyles = makeStyles((theme) => ({ table: { @@ -58,7 +62,7 @@ const colConfigs: ColumnConfig[] = [ }, { label: "Payload", - key: "payload", + key: "task_payload", sortBy: SortBy.Payload, align: "left", }, @@ -82,79 +86,22 @@ const colConfigs: ColumnConfig[] = [ }, ]; -function createData( - id: string, - spec: string, - type: string, - payload: any, - options: string, - nextEnqueue: string, - prevEnqueue: string -) { - return { id, spec, type, payload, options, nextEnqueue, prevEnqueue }; -} - -const rows = [ - createData( - "da0e15bb-3649-45de-9c36-90b9db744b8a", - "*/5 * * * *", - "email:welcome", - { user_id: 42 }, - "[Queue('email')]", - "In 29s", - "4m31s ago" - ), - createData( - "fi0e10bb-3649-45de-9c36-90b9db744b8a", - "* 1 * * *", - "email:daily_digest", - {}, - "[Queue('email')]", - "In 23h", - "1h ago" - ), - createData( - "ca0e17bv-3649-45de-9c36-90b9db744b8a", - "@every 10m", - "search:reindex", - {}, - "[Queue('index')]", - "In 2m", - "8m ago" - ), - createData( - "we4e15bb-3649-45de-9c36-90b9db744b8a", - "*/5 * * * *", - "janitor", - { user_id: 42 }, - "[Queue('low')]", - "In 29s", - "4m31s ago" - ), -]; - -interface Entry { - id: string; - spec: string; - type: string; - payload: any; - options: string; - nextEnqueue: string; - prevEnqueue: string; -} - // sortEntries takes a array of entries and return a sorted array. // It returns a new array and leave the original array untouched. function sortEntries( - entries: Entry[], - cmpFn: (first: Entry, second: Entry) => number -): Entry[] { + entries: SchedulerEntry[], + cmpFn: (first: SchedulerEntry, second: SchedulerEntry) => number +): SchedulerEntry[] { let copy = [...entries]; copy.sort(cmpFn); return copy; } -export default function CronEntriesTable() { +interface Props { + entries: SchedulerEntry[]; +} + +export default function SchedulerEntriesTable(props: Props) { const classes = useStyles(); const [sortBy, setSortBy] = useState(SortBy.EntryId); const [sortDir, setSortDir] = useState(SortDirection.Asc); @@ -171,7 +118,7 @@ export default function CronEntriesTable() { } }; - const cmpFunc = (e1: Entry, q2: Entry): number => { + const cmpFunc = (e1: SchedulerEntry, q2: SchedulerEntry): number => { let isE1Smaller: boolean; switch (sortBy) { case SortBy.EntryId: @@ -183,24 +130,24 @@ export default function CronEntriesTable() { isE1Smaller = e1.spec < q2.spec; break; case SortBy.Type: - if (e1.type === q2.type) return 0; - isE1Smaller = e1.type < q2.type; + if (e1.task_type === q2.task_type) return 0; + isE1Smaller = e1.task_type < q2.task_type; break; case SortBy.Payload: - if (e1.payload === q2.payload) return 0; - isE1Smaller = e1.payload < q2.payload; + if (e1.task_payload === q2.task_payload) return 0; + isE1Smaller = e1.task_payload < q2.task_payload; break; case SortBy.Options: if (e1.options === q2.options) return 0; isE1Smaller = e1.options < q2.options; break; case SortBy.NextEnqueue: - if (e1.nextEnqueue === q2.nextEnqueue) return 0; - isE1Smaller = e1.nextEnqueue < q2.nextEnqueue; + if (e1.next_enqueue_at === q2.next_enqueue_at) return 0; + isE1Smaller = e1.next_enqueue_at < q2.next_enqueue_at; break; case SortBy.PrevEnqueue: - if (e1.prevEnqueue === q2.prevEnqueue) return 0; - isE1Smaller = e1.prevEnqueue < q2.prevEnqueue; + if (e1.prev_enqueue_at === q2.prev_enqueue_at) return 0; + isE1Smaller = e1.prev_enqueue_at < q2.prev_enqueue_at; break; default: // eslint-disable-next-line no-throw-literal @@ -213,6 +160,15 @@ export default function CronEntriesTable() { } }; + if (props.entries.length === 0) { + return ( + + Info + No entries found at this time. + + ); + } + return ( @@ -236,41 +192,43 @@ export default function CronEntriesTable() { - {sortEntries(rows, cmpFunc).map((row, idx) => { - const isLastRow = idx === rows.length - 1; + {sortEntries(props.entries, cmpFunc).map((entry, idx) => { + const isLastRow = idx === props.entries.length - 1; return ( - + - {row.id} + {entry.id} - {row.spec} + {entry.spec} - {row.type} + {entry.task_type} - {JSON.stringify(row.payload)} + {JSON.stringify(entry.task_payload)} - {row.options} + {entry.options.length > 0 + ? entry.options.join(", ") + : "No options"} - {row.nextEnqueue} + {durationBefore(entry.next_enqueue_at)} - {row.prevEnqueue} + {timeAgo(entry.prev_enqueue_at)} ); diff --git a/ui/src/paths.ts b/ui/src/paths.ts index 4a93bb4..14aaf3e 100644 --- a/ui/src/paths.ts +++ b/ui/src/paths.ts @@ -1,7 +1,7 @@ export const paths = { HOME: "/", SETTINGS: "/settings", - CRON: "/cron", + SCHEDULERS: "/schedulers", QUEUE_DETAILS: "/queues/:qname", }; diff --git a/ui/src/timeutil.ts b/ui/src/timeutil.ts index 570352a..ad4eb88 100644 --- a/ui/src/timeutil.ts +++ b/ui/src/timeutil.ts @@ -29,7 +29,7 @@ export function durationBefore(timestamp: string): string { if (duration.totalSeconds < 1) { return "now"; } - return stringifyDuration(duration); + return "in " + stringifyDuration(duration); } catch { return "-"; } diff --git a/ui/src/views/CronView.tsx b/ui/src/views/SchedulersView.tsx similarity index 51% rename from ui/src/views/CronView.tsx rename to ui/src/views/SchedulersView.tsx index 768a74c..ba0066b 100644 --- a/ui/src/views/CronView.tsx +++ b/ui/src/views/SchedulersView.tsx @@ -1,10 +1,14 @@ import React from "react"; +import { connect, ConnectedProps } from "react-redux"; import Container from "@material-ui/core/Container"; import { makeStyles } from "@material-ui/core/styles"; import Grid from "@material-ui/core/Grid"; import Paper from "@material-ui/core/Paper"; -import CronEntriesTable from "../components/CronEntriesTable"; +import SchedulerEntriesTable from "../components/SchedulerEntriesTable"; import Typography from "@material-ui/core/Typography"; +import { AppState } from "../store"; +import { listSchedulerEntriesAsync } from "../actions/schedulerEntriesActions"; +import { usePolling } from "../hooks"; const useStyles = makeStyles((theme) => ({ container: { @@ -19,20 +23,37 @@ const useStyles = makeStyles((theme) => ({ }, heading: { paddingLeft: theme.spacing(2), + marginBottom: theme.spacing(1), }, })); -function CronView() { +function mapStateToProps(state: AppState) { + return { + loading: state.schedulerEntries.loading, + entries: state.schedulerEntries.data, + pollInterval: state.settings.pollInterval, + }; +} + +const connector = connect(mapStateToProps, { listSchedulerEntriesAsync }); + +type Props = ConnectedProps; + +function SchedulersView(props: Props) { + const { pollInterval, listSchedulerEntriesAsync } = props; const classes = useStyles(); + + usePolling(listSchedulerEntriesAsync, pollInterval); + return ( - Cron Entries + Scheduler Entries - + @@ -40,4 +61,4 @@ function CronView() { ); } -export default CronView; +export default connector(SchedulersView);