From 3f9e8820d9320ceef3cb360eefcbb2a94d8832bd Mon Sep 17 00:00:00 2001 From: Ken Hibino Date: Thu, 31 Dec 2020 07:02:54 -0800 Subject: [PATCH] Fetch servers info and show in ServersTable --- ui/src/actions/serversActions.ts | 44 +++++++++++++++++++++++++++++ ui/src/api.ts | 27 +++++++++++++++++- ui/src/components/ServersTable.tsx | 39 ++++++++++++++++++++++++-- ui/src/reducers/serversReducer.ts | 45 ++++++++++++++++++++++++++++++ ui/src/store.ts | 2 ++ ui/src/views/ServersView.tsx | 26 +++++++++++++++-- 6 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 ui/src/actions/serversActions.ts create mode 100644 ui/src/reducers/serversReducer.ts diff --git a/ui/src/actions/serversActions.ts b/ui/src/actions/serversActions.ts new file mode 100644 index 0000000..633082a --- /dev/null +++ b/ui/src/actions/serversActions.ts @@ -0,0 +1,44 @@ +import { Dispatch } from "redux"; +import { listServers, ListServersResponse } from "../api"; + +// List of server related action types. +export const LIST_SERVERS_BEGIN = "LIST_SERVERS_BEGIN"; +export const LIST_SERVERS_SUCCESS = "LIST_SERVERS_SUCCESS"; +export const LIST_SERVERS_ERROR = "LIST_SERVERS_ERROR"; + +interface ListServersBeginAction { + type: typeof LIST_SERVERS_BEGIN; +} +interface ListServersSuccessAction { + type: typeof LIST_SERVERS_SUCCESS; + payload: ListServersResponse; +} +interface ListServersErrorAction { + type: typeof LIST_SERVERS_ERROR; + error: string; // error description +} + +// Union of all server related actions. +export type ServersActionTypes = + | ListServersBeginAction + | ListServersSuccessAction + | ListServersErrorAction; + +export function listServersAsync() { + return async (dispatch: Dispatch) => { + dispatch({ type: LIST_SERVERS_BEGIN }); + try { + const response = await listServers(); + dispatch({ + type: LIST_SERVERS_SUCCESS, + payload: response, + }); + } catch (error) { + console.error("listServersAsync: ", error); + dispatch({ + type: LIST_SERVERS_ERROR, + error: "Could not retrieve servers info", + }); + } + }; +} diff --git a/ui/src/api.ts b/ui/src/api.ts index cabda3a..92065b0 100644 --- a/ui/src/api.ts +++ b/ui/src/api.ts @@ -32,6 +32,10 @@ export interface ListDeadTasksResponse { stats: Queue; } +export interface ListServersResponse { + servers: ServerInfo[]; +} + export interface ListSchedulerEntriesResponse { entries: SchedulerEntry[]; } @@ -129,7 +133,20 @@ export interface DeadTask extends BaseTask { } export interface ServerInfo { - // TODO: fill this out + id: string; + host: string; + pid: number; + concurrency: number; + queue_priorities: { [qname: string]: number }; + strict_priority_enabled: boolean; + start_time: string; + status: string; + active_workers: WorkerInfo[]; +} + +export interface WorkerInfo { + task: ActiveTask; + start_time: string; } export interface SchedulerEntry { @@ -545,6 +562,14 @@ export async function runAllDeadTasks(qname: string): Promise { }); } +export async function listServers(): Promise { + const resp = await axios({ + method: "get", + url: `${BASE_URL}/servers`, + }); + return resp.data; +} + export async function listSchedulerEntries(): Promise { const resp = await axios({ method: "get", diff --git a/ui/src/components/ServersTable.tsx b/ui/src/components/ServersTable.tsx index 7b078af..d04dafe 100644 --- a/ui/src/components/ServersTable.tsx +++ b/ui/src/components/ServersTable.tsx @@ -1,7 +1,6 @@ import React, { useState } from "react"; import clsx from "clsx"; import { makeStyles } from "@material-ui/core/styles"; -import IconButton from "@material-ui/core/IconButton"; import Table from "@material-ui/core/Table"; import TableBody from "@material-ui/core/TableBody"; import TableCell from "@material-ui/core/TableCell"; @@ -13,6 +12,7 @@ import Alert from "@material-ui/lab/Alert"; import AlertTitle from "@material-ui/lab/AlertTitle"; import { ServerInfo } from "../api"; import { SortDirection, SortableTableColumn } from "../types/table"; +import { timeAgo } from "../utils"; const useStyles = makeStyles((theme) => ({ table: { @@ -140,8 +140,43 @@ export default function ServersTable(props: Props) { ))} - + + {sortServerInfos(props.servers, cmpFunc).map((srv) => ( + + ))} + ); } +interface RowProps { + server: ServerInfo; +} + +const useRowStyles = makeStyles((theme) => ({ + rowRoot: { + "& > *": { + borderBottom: "unset", + }, + }, + noBorder: { + border: "none", + }, +})); + +function Row(props: RowProps) { + const classes = useRowStyles(); + const { server } = props; + return ( + + {server.host} + {server.pid} + {server.status} + + {server.active_workers.length}/{server.concurrency} + + {JSON.stringify(server.queue_priorities)} + {timeAgo(server.start_time)} + + ); +} diff --git a/ui/src/reducers/serversReducer.ts b/ui/src/reducers/serversReducer.ts new file mode 100644 index 0000000..d0cce82 --- /dev/null +++ b/ui/src/reducers/serversReducer.ts @@ -0,0 +1,45 @@ +import { + LIST_SERVERS_BEGIN, + LIST_SERVERS_ERROR, + LIST_SERVERS_SUCCESS, + ServersActionTypes, +} from "../actions/serversActions"; +import { ServerInfo } from "../api"; + +interface ServersState { + loading: boolean; + data: ServerInfo[]; +} + +const initialState: ServersState = { + loading: false, + data: [], +}; + +export default function serversReducer( + state = initialState, + action: ServersActionTypes +): ServersState { + switch (action.type) { + case LIST_SERVERS_BEGIN: + return { + ...state, + loading: true, + }; + + case LIST_SERVERS_SUCCESS: + return { + loading: false, + data: action.payload.servers, + }; + + case LIST_SERVERS_ERROR: + return { + ...state, + loading: false, + }; + + default: + return state; + } +} diff --git a/ui/src/store.ts b/ui/src/store.ts index 5fb0c23..3c781f1 100644 --- a/ui/src/store.ts +++ b/ui/src/store.ts @@ -2,6 +2,7 @@ import { combineReducers, configureStore } from "@reduxjs/toolkit"; import settingsReducer from "./reducers/settingsReducer"; import queuesReducer from "./reducers/queuesReducer"; import tasksReducer from "./reducers/tasksReducer"; +import serversReducer from "./reducers/serversReducer"; import schedulerEntriesReducer from "./reducers/schedulerEntriesReducer"; import snackbarReducer from "./reducers/snackbarReducer"; import queueStatsReducer from "./reducers/queueStatsReducer"; @@ -10,6 +11,7 @@ const rootReducer = combineReducers({ settings: settingsReducer, queues: queuesReducer, tasks: tasksReducer, + servers: serversReducer, schedulerEntries: schedulerEntriesReducer, snackbar: snackbarReducer, queueStats: queueStatsReducer, diff --git a/ui/src/views/ServersView.tsx b/ui/src/views/ServersView.tsx index a7f6b1a..3b1c13d 100644 --- a/ui/src/views/ServersView.tsx +++ b/ui/src/views/ServersView.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 Typography from "@material-ui/core/Typography"; import ServersTable from "../components/ServersTable"; +import { listServersAsync } from "../actions/serversActions"; +import { AppState } from "../store"; +import { usePolling } from "../hooks"; const useStyles = makeStyles((theme) => ({ container: { @@ -23,8 +27,24 @@ const useStyles = makeStyles((theme) => ({ }, })); -export default function ServersView() { +function mapStateToProps(state: AppState) { + return { + loading: state.servers.loading, + servers: state.servers.data, + pollInterval: state.settings.pollInterval, + }; +} + +const connector = connect(mapStateToProps, { listServersAsync }); + +type Props = ConnectedProps; + +function ServersView(props: Props) { + const { pollInterval, listServersAsync } = props; const classes = useStyles(); + + usePolling(listServersAsync, pollInterval); + return ( @@ -33,10 +53,12 @@ export default function ServersView() { Servers - + ); } + +export default connector(ServersView);