diff --git a/package.json b/package.json index bcf9fa7..7ab9a64 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "version": "0.0.1", "private": true, "dependencies": { + "@giantmachines/redux-websocket": "^1.5.1", "@material-ui/core": "latest", "@material-ui/icons": "latest", "@types/react": "latest", diff --git a/src/App.tsx b/src/App.tsx index c51bd24..350b933 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -13,6 +13,7 @@ import { HistoricalPlanList } from './HistoricalPlanList'; import { CurrentPlan } from './CurrentPlan'; import { AppBar, Grid, IconButton, Popover, Toolbar } from '@material-ui/core'; import { PlanDrawer } from './PlanDrawer'; +import { ConsoleDrawer } from './ConsoleDrawer'; import { PlanFormContainer } from './PlanFormContainer'; import nsls2Background from "./assets/nsls2_background.png"; import logo from './assets/bluesky-logo.svg'; @@ -93,7 +94,8 @@ interface IState { queue: string; onQueueChange: (queue: string) => void; files: File[]; - drawerOpen: boolean; + actionDrawerOpen: boolean; + consoleDrawerOpen: boolean; planFormVisible: boolean; } @@ -145,7 +147,8 @@ class App extends React.Component { queue: "Start", onQueueChange: this.handleQueueChange, files: [], - drawerOpen: false, + actionDrawerOpen: false, + consoleDrawerOpen: false, planFormVisible: false, }; @@ -166,9 +169,12 @@ class App extends React.Component { - + Actions + + Console + logo { - - + { private handleSelectPlan = (selectedPlan: string) => { - this.closeDrawer() + this.closeActionDrawer() this.showPlanForm() this.setState({ selectedPlan: selectedPlan }); // Check this line this.setState({ editItemUid: ""}); @@ -256,15 +262,31 @@ class App extends React.Component { }); } - private toggleDrawer(){ + private toggleActionDrawer(){ + this.setState({ + actionDrawerOpen: !this.state.actionDrawerOpen + }) + if (this.state.consoleDrawerOpen){ + this.setState({ + consoleDrawerOpen: false + }) + } + } + + private toggleConsoleDrawer(){ this.setState({ - drawerOpen: !this.state.drawerOpen + consoleDrawerOpen: !this.state.consoleDrawerOpen }) + if (this.state.actionDrawerOpen){ + this.setState({ + actionDrawerOpen: false + }) + } } - private closeDrawer(){ + private closeActionDrawer(){ this.setState({ - drawerOpen: false + actionDrawerOpen: false }) } diff --git a/src/ConsoleDrawer.tsx b/src/ConsoleDrawer.tsx new file mode 100644 index 0000000..3533f97 --- /dev/null +++ b/src/ConsoleDrawer.tsx @@ -0,0 +1,80 @@ +import React from 'react'; +import Drawer from '@material-ui/core/Drawer'; +import List from '@material-ui/core/List'; +import ListItem from '@material-ui/core/ListItem'; +import ListItemIcon from '@material-ui/core/ListItemIcon'; +import ListItemText from '@material-ui/core/ListItemText'; +import { addQueueStop, IAllowedPlans, submitExcel } from './queueserver'; +import { Avatar, Box, Button, ListItemSecondaryAction, MenuItem, Typography } from '@material-ui/core'; +import { Star } from '@material-ui/icons'; +import { BulkAdd } from './bulk'; + +type IProps = { + open: boolean +}; + +type IState = { + open: boolean; +} + +const styles = (theme: { zIndex: { drawer: number; }; }) => ({ + appBar: { + // Make the app bar z-index always one more than the drawer z-index + zIndex: theme.zIndex.drawer + 1, + }, +}); + +export class ConsoleDrawer extends React.Component{ + + constructor(props: IProps) { + super(props); + this.state = { + open: true } + } + + render() { + return ( +
+ + +
+ + + + Queue Actions + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ ); + } +} diff --git a/src/HistoricalPlanList.tsx b/src/HistoricalPlanList.tsx index 5741d2d..118f615 100644 --- a/src/HistoricalPlanList.tsx +++ b/src/HistoricalPlanList.tsx @@ -71,7 +71,6 @@ export class HistoricalPlanList extends React.Component run_uids: {JSON.stringify(planObject.result.run_uids)} - diff --git a/src/index.tsx b/src/index.tsx index 0c7bd24..27b1288 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -9,9 +9,9 @@ import { IApplicationState } from './store' import theme from './theme'; import { Routes, Route } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom'; - import App from './App' import UserPage from './user' +import { connect } from '@giantmachines/redux-websocket'; interface IProps { @@ -31,6 +31,8 @@ const Root: React.FunctionComponent = props => { }; export const store = configureStore(); +// Connect websocket +store.dispatch(connect(process.env.REACT_APP_HTTP_SERVER || 'ws://localhost:60610/' + 'stream_console_output')); ReactDOM.render( diff --git a/src/planreducers.ts b/src/planreducers.ts index 9fb841b..8f35c56 100644 --- a/src/planreducers.ts +++ b/src/planreducers.ts @@ -5,7 +5,8 @@ import { IPlanState, IPlanObjectsState, IAllowedPlansState, AllowedPlansActions, AllowedPlansActionTypes, IAllowedPlans, IHistoricalPlansState, HistoricalPlansActions, - HistoricalPlansActionTypes, IStatus } from "./queueserver"; + HistoricalPlansActionTypes, IStatus, + IConsoleOutput, ConsoleOutputActions, ConsoleOutputActionTypes } from "./queueserver"; import { getQueuedPlans, getHistoricalPlans } from './planactions'; import { store } from "./index" @@ -290,3 +291,32 @@ export const queueModifyReducer: Reducer = ( } } }; + + + +const initialConsoleOutputState: IConsoleOutput = { + output: "" +}; + +export const consoleOutputReducer: Reducer = ( + state = initialConsoleOutputState, + action +) => { + switch (action.type) { + case ConsoleOutputActionTypes.OPEN: { + console.log("OPEN REDUCER", action) + return state; + } + case ConsoleOutputActionTypes.CONNECT: { + console.log("CONNECT REDUCER", action) + return state; + } + case ConsoleOutputActionTypes.ERROR: { + console.log("ERROR REDUCER", action) + return state; + } + default: { + return state; + } + } +}; \ No newline at end of file diff --git a/src/queueserver.ts b/src/queueserver.ts index 75938a5..c1920a2 100644 --- a/src/queueserver.ts +++ b/src/queueserver.ts @@ -239,6 +239,41 @@ export type PlanActions = | IPlanModifyQueueAction | IPlanModifyQueueLoadingAction +export enum ConsoleOutputActionTypes { + CONNECT = "REDUX_WEBSOCKET::CONNECT", + ERROR = "REDUX_WEBSOCKET::ERROR", + OPEN = "REDUX_WEBSOCKET::OPEN", + CLOSED = "REDUX_WEBSOCKET::CLOSED", + MESSAGE = "REDUX_WEBSOCKET::MESSAGE" +} + +export interface IConsoleOutputConnectAction { + type: ConsoleOutputActionTypes.CONNECT +} + +export interface IConsoleOutputErrorAction { + type: ConsoleOutputActionTypes.ERROR +} + +export interface IConsoleOutputOpenAction { + type: ConsoleOutputActionTypes.OPEN +} + +export interface IConsoleOutputClosedAction { + type: ConsoleOutputActionTypes.CLOSED +} + +export interface IConsoleOutputMessageAction { + type: ConsoleOutputActionTypes.MESSAGE +} + +export type ConsoleOutputActions = + | IConsoleOutputErrorAction + | IConsoleOutputMessageAction + | IConsoleOutputOpenAction + | IConsoleOutputClosedAction + | IConsoleOutputConnectAction + export interface IPlanState { readonly plan: IPlan; readonly planLoading: boolean; @@ -271,6 +306,10 @@ export interface IStatus { task_results_uid: string } +export interface IConsoleOutput { + output: string +} + export const getStatus = async(): Promise => { const res = await axiosInstance.get('/status'); return res.data; diff --git a/src/store.tsx b/src/store.tsx index 51b9945..d3b99af 100644 --- a/src/store.tsx +++ b/src/store.tsx @@ -1,10 +1,12 @@ import { applyMiddleware, combineReducers, createStore, Store, compose } from "redux" import thunk from "redux-thunk" import { planObjectsReducer, planReducer, planSubmitReducer, - environmentModifyReducer, queueModifyReducer, allowedPlansReducer, historicalPlansReducer, statusReducer } from "./planreducers" -import { IStatus, IPlanState, IPlanObjectsState, IPlanSubmitState, IPlanModifyState, IAllowedPlansState, IHistoricalPlansState} from "./queueserver" + environmentModifyReducer, queueModifyReducer, allowedPlansReducer, historicalPlansReducer, statusReducer, consoleOutputReducer } from "./planreducers" +import { IStatus, IPlanState, IPlanObjectsState, IPlanSubmitState, IPlanModifyState, IAllowedPlansState, IHistoricalPlansState, IConsoleOutput} from "./queueserver" import { userReducer } from "./userreducers" import { IUserState } from "./facility" +import reduxWebsocket from '@giantmachines/redux-websocket'; + export interface IApplicationState { plan: IPlanState; @@ -16,6 +18,7 @@ export interface IApplicationState { queue: IPlanModifyState; user: IUserState; status: IStatus; + output: IConsoleOutput; } const rootReducer = combineReducers({ @@ -28,10 +31,12 @@ const rootReducer = combineReducers({ queue: queueModifyReducer, user: userReducer, status: statusReducer, + output: consoleOutputReducer }) export default function configureStore(): Store { const composeEnhancers = (window as any).__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose; - const store = createStore(rootReducer, undefined, composeEnhancers(applyMiddleware(thunk))); + const reduxWebsocketMiddleware = reduxWebsocket(); + const store = createStore(rootReducer, undefined, composeEnhancers(applyMiddleware(thunk, reduxWebsocketMiddleware))); return store; } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ed9429a..9655ebd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1084,6 +1084,13 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@giantmachines/redux-websocket@^1.5.1": + version "1.5.1" + resolved "https://registry.yarnpkg.com/@giantmachines/redux-websocket/-/redux-websocket-1.5.1.tgz#90a335f531d31a54e1217d051014cde0f69dd28b" + integrity sha512-s/vyWsfjyCK7lRWgRk4gjV1mg0E6kjxcKE1y4wUrE4kBf3Vvtj64g/fIe1w6Jw5UaoABrEjm4XxQVOXOAaOSAg== + dependencies: + redux "~4" + "@humanwhocodes/config-array@^0.9.2": version "0.9.2" resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz" @@ -7292,7 +7299,7 @@ redux-thunk@*, redux-thunk@^2.3.0: resolved "https://registry.yarnpkg.com/redux-thunk/-/redux-thunk-2.4.1.tgz" integrity sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q== -redux@^4.0.0, redux@^4.0.5: +redux@^4.0.0, redux@^4.0.5, redux@~4: version "4.1.2" resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz" integrity sha512-SH8PglcebESbd/shgf6mii6EIoRM0zrQyjcuQ+ojmfxjTtE0z9Y8pa62iA/OJ58qjP6j27uyW4kUF4jl/jd6sw==