From 634feb597a3217d332b010b388501d02b4ea9a99 Mon Sep 17 00:00:00 2001 From: emizzle Date: Wed, 1 May 2019 15:59:07 +1000 Subject: [PATCH] feat(@embark/api): Add command `service api on/off` Add support for `service api on/off` commands. Deprecate commands `api start/stop` in favor of `service api on/off`. `service api on` - Enables the API server serving Cockpit. Shows an error if the API server is already starting or started. `service api off` - Disables the API server serving Cockpit. Shows an error if the API server is already stopping or stopped. `api start` - This command has been deprecated in favor of `service api on` and will be removed in future versions. `api stop` - This command has been deprecated in favor of `service api off` and will be removed in future versions. `api:start` - This event has been deprecated and will be removed in future versions. `api:stop` - This event has been deprecated and will be removed in future versions. --- packages/embark-api/src/index.ts | 91 ++++++++++++------- packages/embark-api/src/server.ts | 21 ++++- packages/embark-typings/src/logger.d.ts | 1 + .../src/containers/ContractsContainer.js | 21 ++++- .../embark-ui/src/containers/HomeContainer.js | 29 ++---- .../src/containers/ServicesContainer.js | 6 +- 6 files changed, 109 insertions(+), 60 deletions(-) diff --git a/packages/embark-api/src/index.ts b/packages/embark-api/src/index.ts index bef8a8c18..2afc698ed 100644 --- a/packages/embark-api/src/index.ts +++ b/packages/embark-api/src/index.ts @@ -11,6 +11,7 @@ export default class Api { private port!: number; private api!: Server; private apiUrl!: string; + private isServiceRegistered = false; constructor(private embark: Embark, private options: any) { this.embark.events.emit("status", __("Starting API & Cockpit UI")); @@ -22,54 +23,70 @@ export default class Api { this.listenToCommands(); this.registerConsoleCommands(); + this.init(); + }); + } - this.embark.events.request("processes:register", "api", { - launchFn: (cb: (error: Error | null, message: string) => void) => { - this.api.start() - .then(() => cb(null, __("Cockpit UI available at %s", this.apiUrl))) - .catch((error: Error) => cb(error, "")); - }, - stopFn: (cb: (error: Error | null, message: string) => void) => { - this.api.stop() - .then(() => cb(null, __("Cockpit UI stopped"))) - .catch((error: Error) => cb(error, "")); - }, - }); + private init() { + this.embark.events.request("processes:register", "api", { + launchFn: (cb: (error: Error | null, message: string) => void) => { + this.api.start() + .then(() => cb(null, __("Cockpit UI available at %s", this.apiUrl))) + .catch((error: Error) => cb(error, "")); + }, + stopFn: (cb: (error: Error | null, message: string) => void) => { + this.api.stop() + .then(() => cb(null, __("Cockpit UI stopped"))) + .catch((error: Error) => cb(error, "")); + }, + }); - this.embark.events.request("processes:launch", "api", (error: Error | null, message: string) => { - if (error) { - this.embark.logger.error(error.message); - } else { - this.embark.logger.info(message); - } - this.setServiceCheck(); - }); + this.embark.events.request("processes:launch", "api", (error: Error | null, message: string) => { + if (error) { + this.embark.logger.error(error.message); + } else { + this.embark.logger.info(message); + } + this.setServiceCheck(); + }); + + this.embark.events.on("check:wentOffline:api", () => { + this.embark.logger.info(__("Cockpit is offline, please close Cockpit.")); + }); + this.embark.events.on("check:backOnline:api", () => { + this.embark.logger.info(__("Cockpit is online, please open/refresh Cockpit.")); }); } private setServiceCheck() { + if (this.isServiceRegistered) { + return; + } + this.isServiceRegistered = true; this.embark.events.request("services:register", "api", (cb: (options: object) => any) => { checkIsAvailable(this.apiUrl, (isAvailable: boolean) => { const devServer = __("Cockpit UI") + " (" + this.apiUrl + ")"; const serverStatus = (isAvailable ? "on" : "off"); - return cb({name: devServer, status: serverStatus}); + return cb({ name: devServer, status: serverStatus }); }); }); - - this.embark.events.on("check:wentOffline:api", () => { - this.embark.logger.info(__("Cockpit UI is offline")); - }); } private listenToCommands() { this.embark.events.setCommandHandler("api:url", (cb) => cb(this.apiUrl)); - this.embark.events.setCommandHandler("api:start", (cb) => this.embark.events.request("processes:launch", "api", cb)); - this.embark.events.setCommandHandler("api:stop", (cb) => this.embark.events.request("processes:stop", "api", cb)); - this.embark.events.setCommandHandler("logs:api:enable", (cb) => { + this.embark.events.setCommandHandler("api:start", (cb) => { + this.embark.logger.warn(__("The event 'api:start' has been deprecated and will be removed in future versions.")); + this.embark.events.request("processes:launch", "api", cb); + }); + this.embark.events.setCommandHandler("api:stop", (cb) => { + this.embark.logger.warn(__("The event 'api:stop' has been deprecated and will be removed in future versions.")); + this.embark.events.request("processes:stop", "api", cb); + }); + this.embark.events.setCommandHandler("logs:api:enable", (cb) => { this.api.enableLogging(); cb(); }); - this.embark.events.setCommandHandler("logs:api:disable", (cb) => { + this.embark.events.setCommandHandler("logs:api:disable", (cb) => { this.api.disableLogging(); cb(); }); @@ -79,16 +96,24 @@ export default class Api { this.embark.registerConsoleCommand({ description: __("Start or stop the API"), matches: ["api start"], - process: (cmd: string, callback: () => void) => { - this.embark.events.request("api:start", callback); + process: (cmd: string, callback: (msg: string) => void) => { + const message = __("The command 'api:start' has been deprecated in favor of 'service api on' and will be removed in future versions."); + this.embark.logger.warn(message); // logs to Embark's console + this.embark.events.request("processes:launch", "api", (err: string, msg: string) => { + callback(err || msg); // logs to Cockpit's console + }); }, usage: "api start/stop", }); this.embark.registerConsoleCommand({ matches: ["api stop"], - process: (cmd: string, callback: () => void) => { - this.embark.events.request("api:stop", callback); + process: (cmd: string, callback: (msg: string) => void) => { + const message = __("The command 'api:stop' has been deprecated in favor of 'service api off' and will be removed in future versions."); + this.embark.logger.warn(message); // logs to Embark's console + this.embark.events.request("processes:stop", "api", (err: string, msg: string) => { + callback(err || msg); // logs to Cockpit's console + }); }, }); diff --git a/packages/embark-api/src/server.ts b/packages/embark-api/src/server.ts index dcb073561..8ce7dc84d 100644 --- a/packages/embark-api/src/server.ts +++ b/packages/embark-api/src/server.ts @@ -9,6 +9,7 @@ import expressWs from "express-ws"; import findUp from "find-up"; import helmet from "helmet"; import * as http from "http"; +import * as net from "net"; import * as path from "path"; import * as ws from "ws"; @@ -28,6 +29,8 @@ export default class Server { private isLogging: boolean = false; private server?: http.Server; + private openSockets = new Set(); + constructor(private embark: Embark, private port: number, private hostname: string, private plugins: Plugins) { this.expressInstance = this.initApp(); this.embarkUiBuildDir = (findUp.sync("node_modules/embark-ui/build", {cwd: embarkPath()}) || embarkPath("node_modules/embark-ui/build")); @@ -67,6 +70,15 @@ export default class Server { this.server = this.expressInstance.app.listen(this.port, this.hostname, () => { resolve(); }); + + // keep track of our open websockets so we can destroy them + // if the api server is shutdown + this.server.on("connection", (socket) => { + this.openSockets.add(socket); + socket.on("close", () => { + this.openSockets.delete(socket); + }); + }); }); } @@ -77,6 +89,9 @@ export default class Server { return reject(new Error(message)); } + // close any open sockets + this.openSockets.forEach((socket) => socket.destroy()); + this.server.close(() => { this.server = undefined; resolve(); @@ -198,17 +213,17 @@ export default class Server { instance.app.use(cors()); instance.app.use(bodyParser.json()); - instance.app.use(bodyParser.urlencoded({extended: true})); + instance.app.use(bodyParser.urlencoded({ extended: true })); instance.app.ws("/logs", (websocket: ws, _req: Request) => { this.embark.events.on("log", (level: string, message: string) => { - websocket.send(JSON.stringify({msg: message, msg_clear: message.stripColors, logLevel: level}), () => {}); + websocket.send(JSON.stringify({ msg: message, msg_clear: message.stripColors, logLevel: level }), () => { }); }); }); if (this.plugins) { instance.app.get("/embark-api/plugins", (_req, res: Response) => { - res.send(JSON.stringify(this.plugins.plugins.map((plugin) => ({name: plugin.name})))); + res.send(JSON.stringify(this.plugins.plugins.map((plugin) => ({ name: plugin.name })))); }); const callDescriptions: CallDescription[] = this.plugins.getPluginsProperty("apiCalls", "apiCalls"); diff --git a/packages/embark-typings/src/logger.d.ts b/packages/embark-typings/src/logger.d.ts index 4cbb33775..b2c7aba81 100644 --- a/packages/embark-typings/src/logger.d.ts +++ b/packages/embark-typings/src/logger.d.ts @@ -1,4 +1,5 @@ export interface Logger { info(text: string): void; + warn(text: string): void; error(text: string, ...args: Array): void; } diff --git a/packages/embark-ui/src/containers/ContractsContainer.js b/packages/embark-ui/src/containers/ContractsContainer.js index 36e71889b..1f6016ba8 100644 --- a/packages/embark-ui/src/containers/ContractsContainer.js +++ b/packages/embark-ui/src/containers/ContractsContainer.js @@ -8,6 +8,8 @@ import Contracts from '../components/Contracts'; import ContractsList from '../components/ContractsList'; import {getContracts} from "../reducers/selectors"; import PageHead from "../components/PageHead"; +import Loading from '../components/Loading'; +import Error from '../components/Error'; const MAX_CONTRACTS = 10; @@ -78,16 +80,25 @@ class ContractsContainer extends Component { } render() { + const {error, loading, mode, updatePageHeader} = this.props; + if (error) { + return ; + } + + if (loading) { + return ; + } + this.resetNums(); let ContractsComp; - if (this.props.mode === "detail") { + if (mode === "detail") { ContractsComp = Contracts - } else if (this.props.mode === "list") { + } else if (mode === "list") { ContractsComp = ContractsList } return ( - {this.props.updatePageHeader && + {updatePageHeader && } )} /> - 0} {...this.props} render={({contracts}) => ( - - - Deployed Contracts -
- -
-
-
- )} /> - + + + Deployed Contracts +
+ +
+
+
); @@ -97,16 +92,13 @@ HomeContainer.propTypes = { stopProcessLogs: PropTypes.func, fetchProcessLogs: PropTypes.func, listenToProcessLogs: PropTypes.func, - fetchContracts: PropTypes.func, - services: PropTypes.array, - contracts: PropTypes.array + services: PropTypes.array }; function mapStateToProps(state) { return { processes: getProcesses(state), services: getServices(state), - contracts: getContracts(state), error: state.errorMessage, processLogs: getProcessLogs(state), commandSuggestions: getCommandSuggestions(state), @@ -120,7 +112,6 @@ export default connect( postCommand: commandsAction.post, postCommandSuggestions: commandSuggestionsAction.post, fetchProcessLogs: processLogsAction.request, - fetchContracts: contractsAction.request, listenToProcessLogs, stopProcessLogs } diff --git a/packages/embark-ui/src/containers/ServicesContainer.js b/packages/embark-ui/src/containers/ServicesContainer.js index f0981ac2f..b91f9cc22 100644 --- a/packages/embark-ui/src/containers/ServicesContainer.js +++ b/packages/embark-ui/src/containers/ServicesContainer.js @@ -31,11 +31,15 @@ class ServicesContainer extends Component { ServicesContainer.propTypes = { fetchServices: PropTypes.func, listenToServices: PropTypes.func, + error: PropTypes.string, + loading: PropTypes.bool }; function mapStateToProps(state, _props) { return { - services: getServices(state) + services: getServices(state), + error: state.errorMessage, + loading: state.loading }; }