mirror of https://github.com/embarklabs/embark.git
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.
This commit is contained in:
parent
3a07d34cc2
commit
634feb597a
|
@ -11,6 +11,7 @@ export default class Api {
|
||||||
private port!: number;
|
private port!: number;
|
||||||
private api!: Server;
|
private api!: Server;
|
||||||
private apiUrl!: string;
|
private apiUrl!: string;
|
||||||
|
private isServiceRegistered = false;
|
||||||
|
|
||||||
constructor(private embark: Embark, private options: any) {
|
constructor(private embark: Embark, private options: any) {
|
||||||
this.embark.events.emit("status", __("Starting API & Cockpit UI"));
|
this.embark.events.emit("status", __("Starting API & Cockpit UI"));
|
||||||
|
@ -22,54 +23,70 @@ export default class Api {
|
||||||
|
|
||||||
this.listenToCommands();
|
this.listenToCommands();
|
||||||
this.registerConsoleCommands();
|
this.registerConsoleCommands();
|
||||||
|
this.init();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.embark.events.request("processes:register", "api", {
|
private init() {
|
||||||
launchFn: (cb: (error: Error | null, message: string) => void) => {
|
this.embark.events.request("processes:register", "api", {
|
||||||
this.api.start()
|
launchFn: (cb: (error: Error | null, message: string) => void) => {
|
||||||
.then(() => cb(null, __("Cockpit UI available at %s", this.apiUrl)))
|
this.api.start()
|
||||||
.catch((error: Error) => cb(error, ""));
|
.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()
|
stopFn: (cb: (error: Error | null, message: string) => void) => {
|
||||||
.then(() => cb(null, __("Cockpit UI stopped")))
|
this.api.stop()
|
||||||
.catch((error: Error) => cb(error, ""));
|
.then(() => cb(null, __("Cockpit UI stopped")))
|
||||||
},
|
.catch((error: Error) => cb(error, ""));
|
||||||
});
|
},
|
||||||
|
});
|
||||||
|
|
||||||
this.embark.events.request("processes:launch", "api", (error: Error | null, message: string) => {
|
this.embark.events.request("processes:launch", "api", (error: Error | null, message: string) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
this.embark.logger.error(error.message);
|
this.embark.logger.error(error.message);
|
||||||
} else {
|
} else {
|
||||||
this.embark.logger.info(message);
|
this.embark.logger.info(message);
|
||||||
}
|
}
|
||||||
this.setServiceCheck();
|
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() {
|
private setServiceCheck() {
|
||||||
|
if (this.isServiceRegistered) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isServiceRegistered = true;
|
||||||
this.embark.events.request("services:register", "api", (cb: (options: object) => any) => {
|
this.embark.events.request("services:register", "api", (cb: (options: object) => any) => {
|
||||||
checkIsAvailable(this.apiUrl, (isAvailable: boolean) => {
|
checkIsAvailable(this.apiUrl, (isAvailable: boolean) => {
|
||||||
const devServer = __("Cockpit UI") + " (" + this.apiUrl + ")";
|
const devServer = __("Cockpit UI") + " (" + this.apiUrl + ")";
|
||||||
const serverStatus = (isAvailable ? "on" : "off");
|
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() {
|
private listenToCommands() {
|
||||||
this.embark.events.setCommandHandler("api:url", (cb) => cb(this.apiUrl));
|
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:start", (cb) => {
|
||||||
this.embark.events.setCommandHandler("api:stop", (cb) => this.embark.events.request("processes:stop", "api", cb));
|
this.embark.logger.warn(__("The event 'api:start' has been deprecated and will be removed in future versions."));
|
||||||
this.embark.events.setCommandHandler("logs:api:enable", (cb) => {
|
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();
|
this.api.enableLogging();
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
this.embark.events.setCommandHandler("logs:api:disable", (cb) => {
|
this.embark.events.setCommandHandler("logs:api:disable", (cb) => {
|
||||||
this.api.disableLogging();
|
this.api.disableLogging();
|
||||||
cb();
|
cb();
|
||||||
});
|
});
|
||||||
|
@ -79,16 +96,24 @@ export default class Api {
|
||||||
this.embark.registerConsoleCommand({
|
this.embark.registerConsoleCommand({
|
||||||
description: __("Start or stop the API"),
|
description: __("Start or stop the API"),
|
||||||
matches: ["api start"],
|
matches: ["api start"],
|
||||||
process: (cmd: string, callback: () => void) => {
|
process: (cmd: string, callback: (msg: string) => void) => {
|
||||||
this.embark.events.request("api:start", callback);
|
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",
|
usage: "api start/stop",
|
||||||
});
|
});
|
||||||
|
|
||||||
this.embark.registerConsoleCommand({
|
this.embark.registerConsoleCommand({
|
||||||
matches: ["api stop"],
|
matches: ["api stop"],
|
||||||
process: (cmd: string, callback: () => void) => {
|
process: (cmd: string, callback: (msg: string) => void) => {
|
||||||
this.embark.events.request("api:stop", callback);
|
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
|
||||||
|
});
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import expressWs from "express-ws";
|
||||||
import findUp from "find-up";
|
import findUp from "find-up";
|
||||||
import helmet from "helmet";
|
import helmet from "helmet";
|
||||||
import * as http from "http";
|
import * as http from "http";
|
||||||
|
import * as net from "net";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import * as ws from "ws";
|
import * as ws from "ws";
|
||||||
|
|
||||||
|
@ -28,6 +29,8 @@ export default class Server {
|
||||||
private isLogging: boolean = false;
|
private isLogging: boolean = false;
|
||||||
private server?: http.Server;
|
private server?: http.Server;
|
||||||
|
|
||||||
|
private openSockets = new Set<net.Socket>();
|
||||||
|
|
||||||
constructor(private embark: Embark, private port: number, private hostname: string, private plugins: Plugins) {
|
constructor(private embark: Embark, private port: number, private hostname: string, private plugins: Plugins) {
|
||||||
this.expressInstance = this.initApp();
|
this.expressInstance = this.initApp();
|
||||||
this.embarkUiBuildDir = (findUp.sync("node_modules/embark-ui/build", {cwd: embarkPath()}) || embarkPath("node_modules/embark-ui/build"));
|
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, () => {
|
this.server = this.expressInstance.app.listen(this.port, this.hostname, () => {
|
||||||
resolve();
|
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));
|
return reject(new Error(message));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// close any open sockets
|
||||||
|
this.openSockets.forEach((socket) => socket.destroy());
|
||||||
|
|
||||||
this.server.close(() => {
|
this.server.close(() => {
|
||||||
this.server = undefined;
|
this.server = undefined;
|
||||||
resolve();
|
resolve();
|
||||||
|
@ -198,17 +213,17 @@ export default class Server {
|
||||||
instance.app.use(cors());
|
instance.app.use(cors());
|
||||||
|
|
||||||
instance.app.use(bodyParser.json());
|
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) => {
|
instance.app.ws("/logs", (websocket: ws, _req: Request) => {
|
||||||
this.embark.events.on("log", (level: string, message: string) => {
|
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) {
|
if (this.plugins) {
|
||||||
instance.app.get("/embark-api/plugins", (_req, res: Response) => {
|
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");
|
const callDescriptions: CallDescription[] = this.plugins.getPluginsProperty("apiCalls", "apiCalls");
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
export interface Logger {
|
export interface Logger {
|
||||||
info(text: string): void;
|
info(text: string): void;
|
||||||
|
warn(text: string): void;
|
||||||
error(text: string, ...args: Array<string|Error>): void;
|
error(text: string, ...args: Array<string|Error>): void;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,8 @@ import Contracts from '../components/Contracts';
|
||||||
import ContractsList from '../components/ContractsList';
|
import ContractsList from '../components/ContractsList';
|
||||||
import {getContracts} from "../reducers/selectors";
|
import {getContracts} from "../reducers/selectors";
|
||||||
import PageHead from "../components/PageHead";
|
import PageHead from "../components/PageHead";
|
||||||
|
import Loading from '../components/Loading';
|
||||||
|
import Error from '../components/Error';
|
||||||
|
|
||||||
const MAX_CONTRACTS = 10;
|
const MAX_CONTRACTS = 10;
|
||||||
|
|
||||||
|
@ -78,16 +80,25 @@ class ContractsContainer extends Component {
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
|
const {error, loading, mode, updatePageHeader} = this.props;
|
||||||
|
if (error) {
|
||||||
|
return <Error error={error} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
this.resetNums();
|
this.resetNums();
|
||||||
let ContractsComp;
|
let ContractsComp;
|
||||||
if (this.props.mode === "detail") {
|
if (mode === "detail") {
|
||||||
ContractsComp = Contracts
|
ContractsComp = Contracts
|
||||||
} else if (this.props.mode === "list") {
|
} else if (mode === "list") {
|
||||||
ContractsComp = ContractsList
|
ContractsComp = ContractsList
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<React.Fragment>
|
<React.Fragment>
|
||||||
{this.props.updatePageHeader &&
|
{updatePageHeader &&
|
||||||
<PageHead title="Contracts"
|
<PageHead title="Contracts"
|
||||||
description="Summary of all deployed contracts" />}
|
description="Summary of all deployed contracts" />}
|
||||||
<ContractsComp contracts={this.currentContracts}
|
<ContractsComp contracts={this.currentContracts}
|
||||||
|
@ -115,7 +126,9 @@ ContractsContainer.propTypes = {
|
||||||
stopContracts: PropTypes.func,
|
stopContracts: PropTypes.func,
|
||||||
fiddleContracts: PropTypes.array,
|
fiddleContracts: PropTypes.array,
|
||||||
mode: PropTypes.string,
|
mode: PropTypes.string,
|
||||||
updatePageHeader: PropTypes.bool
|
updatePageHeader: PropTypes.bool,
|
||||||
|
error: PropTypes.string,
|
||||||
|
loading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
ContractsContainer.defaultProps = {
|
ContractsContainer.defaultProps = {
|
||||||
|
|
|
@ -9,7 +9,6 @@ import {
|
||||||
} from 'reactstrap';
|
} from 'reactstrap';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
contracts as contractsAction,
|
|
||||||
commands as commandsAction,
|
commands as commandsAction,
|
||||||
commandSuggestions as commandSuggestionsAction,
|
commandSuggestions as commandSuggestionsAction,
|
||||||
listenToProcessLogs,
|
listenToProcessLogs,
|
||||||
|
@ -22,7 +21,7 @@ import Console from '../components/Console';
|
||||||
import {EMBARK_PROCESS_NAME, LOG_LIMIT} from '../constants';
|
import {EMBARK_PROCESS_NAME, LOG_LIMIT} from '../constants';
|
||||||
import PageHead from '../components/PageHead';
|
import PageHead from '../components/PageHead';
|
||||||
import ServicesContainer from './ServicesContainer';
|
import ServicesContainer from './ServicesContainer';
|
||||||
import {getContracts, getProcesses, getProcessLogs, getServices, getCommandSuggestions} from "../reducers/selectors";
|
import {getProcesses, getProcessLogs, getServices, getCommandSuggestions} from "../reducers/selectors";
|
||||||
import ContractsContainer from "./ContractsContainer";
|
import ContractsContainer from "./ContractsContainer";
|
||||||
|
|
||||||
class HomeContainer extends Component {
|
class HomeContainer extends Component {
|
||||||
|
@ -45,7 +44,6 @@ class HomeContainer extends Component {
|
||||||
this.props.fetchProcessLogs(processName, LOG_LIMIT);
|
this.props.fetchProcessLogs(processName, LOG_LIMIT);
|
||||||
this.props.listenToProcessLogs(processName);
|
this.props.listenToProcessLogs(processName);
|
||||||
|
|
||||||
this.props.fetchContracts();
|
|
||||||
this.setState({activeProcess: processName});
|
this.setState({activeProcess: processName});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,17 +69,14 @@ class HomeContainer extends Component {
|
||||||
</Card>
|
</Card>
|
||||||
)} />
|
)} />
|
||||||
|
|
||||||
<DataWrapper shouldRender={this.props.contracts.length > 0} {...this.props} render={({contracts}) => (
|
<Card>
|
||||||
<Card>
|
<CardBody>
|
||||||
<CardBody>
|
<CardTitle>Deployed Contracts</CardTitle>
|
||||||
<CardTitle>Deployed Contracts</CardTitle>
|
<div style={{marginBottom: '1.5rem', overflow: 'auto'}}>
|
||||||
<div style={{marginBottom: '1.5rem', overflow: 'auto'}}>
|
<ContractsContainer mode="list" numContractsToDisplay={5} updatePageHeader={false} />
|
||||||
<ContractsContainer contracts={contracts} mode="list" numContractsToDisplay={5} updatePageHeader={false} />
|
</div>
|
||||||
</div>
|
</CardBody>
|
||||||
</CardBody>
|
</Card>
|
||||||
</Card>
|
|
||||||
)} />
|
|
||||||
|
|
||||||
|
|
||||||
</React.Fragment>
|
</React.Fragment>
|
||||||
);
|
);
|
||||||
|
@ -97,16 +92,13 @@ HomeContainer.propTypes = {
|
||||||
stopProcessLogs: PropTypes.func,
|
stopProcessLogs: PropTypes.func,
|
||||||
fetchProcessLogs: PropTypes.func,
|
fetchProcessLogs: PropTypes.func,
|
||||||
listenToProcessLogs: PropTypes.func,
|
listenToProcessLogs: PropTypes.func,
|
||||||
fetchContracts: PropTypes.func,
|
services: PropTypes.array
|
||||||
services: PropTypes.array,
|
|
||||||
contracts: PropTypes.array
|
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state) {
|
function mapStateToProps(state) {
|
||||||
return {
|
return {
|
||||||
processes: getProcesses(state),
|
processes: getProcesses(state),
|
||||||
services: getServices(state),
|
services: getServices(state),
|
||||||
contracts: getContracts(state),
|
|
||||||
error: state.errorMessage,
|
error: state.errorMessage,
|
||||||
processLogs: getProcessLogs(state),
|
processLogs: getProcessLogs(state),
|
||||||
commandSuggestions: getCommandSuggestions(state),
|
commandSuggestions: getCommandSuggestions(state),
|
||||||
|
@ -120,7 +112,6 @@ export default connect(
|
||||||
postCommand: commandsAction.post,
|
postCommand: commandsAction.post,
|
||||||
postCommandSuggestions: commandSuggestionsAction.post,
|
postCommandSuggestions: commandSuggestionsAction.post,
|
||||||
fetchProcessLogs: processLogsAction.request,
|
fetchProcessLogs: processLogsAction.request,
|
||||||
fetchContracts: contractsAction.request,
|
|
||||||
listenToProcessLogs,
|
listenToProcessLogs,
|
||||||
stopProcessLogs
|
stopProcessLogs
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,11 +31,15 @@ class ServicesContainer extends Component {
|
||||||
ServicesContainer.propTypes = {
|
ServicesContainer.propTypes = {
|
||||||
fetchServices: PropTypes.func,
|
fetchServices: PropTypes.func,
|
||||||
listenToServices: PropTypes.func,
|
listenToServices: PropTypes.func,
|
||||||
|
error: PropTypes.string,
|
||||||
|
loading: PropTypes.bool
|
||||||
};
|
};
|
||||||
|
|
||||||
function mapStateToProps(state, _props) {
|
function mapStateToProps(state, _props) {
|
||||||
return {
|
return {
|
||||||
services: getServices(state)
|
services: getServices(state),
|
||||||
|
error: state.errorMessage,
|
||||||
|
loading: state.loading
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue