637 lines
19 KiB
TypeScript
637 lines
19 KiB
TypeScript
/**
|
|
* @author Martin Karkowski
|
|
* @email m.karkowski@zema.de
|
|
* @create date 2021-07-27 15:45:00
|
|
* @modify date 2021-07-27 15:45:00
|
|
* @desc [description]
|
|
*/
|
|
|
|
import { ArgumentParser } from "argparse";
|
|
import * as inquirer from "inquirer";
|
|
import "reflect-metadata";
|
|
import {
|
|
getLayer,
|
|
layerDefaultParameters,
|
|
validLayers,
|
|
} from "../communication/getLayer.nodejs";
|
|
import { getDispatcher } from "../dispatcher/getDispatcher";
|
|
import { dynamicSort } from "../helpers/arrayMethods";
|
|
import { objectToMap } from "../helpers/objectMethods";
|
|
import { convertPath } from "../helpers/path";
|
|
import { padString } from "../helpers/stringMethods";
|
|
import { getNopeLogger } from "../logger/index.browser";
|
|
import { LoggerLevels } from "../logger/nopeLogger";
|
|
import {
|
|
ICommunicationBridge,
|
|
INopeModuleDescription,
|
|
INopeObserver,
|
|
} from "../types/nope";
|
|
inquirer.registerPrompt("search-list", require("inquirer-search-list"));
|
|
|
|
/**
|
|
* Helper to parse JSON-Input.
|
|
*
|
|
* @author M.Karkowski
|
|
* @return {*}
|
|
*/
|
|
async function getJsonInput() {
|
|
let data: any = null;
|
|
while (data == null) {
|
|
data = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "input",
|
|
message: "Please enter valid JSON.",
|
|
name: "data",
|
|
},
|
|
])
|
|
).data;
|
|
try {
|
|
return JSON.parse(data);
|
|
} catch (e) {
|
|
data = null;
|
|
}
|
|
}
|
|
|
|
return JSON.parse(data);
|
|
}
|
|
|
|
export async function interact(
|
|
additionalArguments: {
|
|
help: string;
|
|
type: "string" | "number";
|
|
name: string | string;
|
|
defaultValue?: any;
|
|
}[] = []
|
|
): Promise<void> {
|
|
const parser = new ArgumentParser({
|
|
// version: "1.0.0",
|
|
add_help: true,
|
|
description: "Command Line interface, to analyze the runtime.",
|
|
});
|
|
|
|
for (const arg of additionalArguments) {
|
|
parser.add_argument(arg.name, {
|
|
help: arg.help,
|
|
defaultValue: arg.defaultValue,
|
|
type: arg.type,
|
|
});
|
|
}
|
|
|
|
parser.add_argument("-s", "--server", {
|
|
help: "The Server to Use.",
|
|
defaultValue: "localhost",
|
|
type: "str",
|
|
dest: "uri",
|
|
});
|
|
|
|
parser.add_argument("-p", "--port", {
|
|
help: "The Port the Connector",
|
|
defaultValue: 7000,
|
|
type: "int",
|
|
dest: "port",
|
|
});
|
|
|
|
parser.add_argument("-c", "--channel", {
|
|
help:
|
|
"The Communication Channel, which should be used. Possible Values are: " +
|
|
// Display all Options:
|
|
Object.getOwnPropertyNames(validLayers)
|
|
.map((item) => '"' + item + '"')
|
|
.join(", "),
|
|
defaultValue: "event",
|
|
type: "str",
|
|
dest: "channel",
|
|
});
|
|
|
|
parser.add_argument(["-l", "--log"], {
|
|
help:
|
|
'Specify the Logger Level. Defaults to "info". Valid values are: ' +
|
|
LoggerLevels.join(", "),
|
|
defaultValue: "info",
|
|
type: "str",
|
|
dest: "log",
|
|
});
|
|
|
|
const args = parser.parse_args();
|
|
|
|
if (!Object.getOwnPropertyNames(validLayers).includes(args.channel)) {
|
|
console.error(
|
|
"Invalid Channel. Please use the following values. " +
|
|
Object.getOwnPropertyNames(validLayers)
|
|
.map((item) => '"' + item + '"')
|
|
.join(", ")
|
|
);
|
|
return;
|
|
}
|
|
|
|
if (args.channel === "io-client") {
|
|
args.params = "http://" + args.uri + ":" + args.port.toString();
|
|
} else {
|
|
// Assign the Default Setting for the Channel.
|
|
args.params = layerDefaultParameters[args.channel];
|
|
}
|
|
|
|
// Define a Logger
|
|
const logger = getNopeLogger("nope-cli-tool");
|
|
|
|
try {
|
|
logger.info(
|
|
"Connecting to http://" + args.uri + ":" + args.port.toString()
|
|
);
|
|
|
|
const dispatcher = getDispatcher({
|
|
communicator: getLayer(
|
|
args.channel,
|
|
args.params,
|
|
args.log
|
|
) as ICommunicationBridge,
|
|
logger,
|
|
id: "nope-cli-tool",
|
|
});
|
|
|
|
await dispatcher.ready.waitFor((value) => value);
|
|
|
|
logger.info("Connected to http://" + args.uri + ":" + args.port.toString());
|
|
|
|
// Now we wait for the User to Select an Action:
|
|
let exit = false;
|
|
|
|
const choices = [
|
|
{
|
|
name: "inspect - Operations to inspect the NoPE-System",
|
|
value: "inspect",
|
|
type: "menu",
|
|
items: [
|
|
{
|
|
name: "instances - Show available Instances",
|
|
value: "showInstances",
|
|
},
|
|
{
|
|
name: "types - Show available Types",
|
|
value: "showTypes",
|
|
},
|
|
{
|
|
name: "services - Show available Services",
|
|
value: "showServices",
|
|
},
|
|
{
|
|
name: "hosts - Show available Hosts",
|
|
value: "showHosts",
|
|
},
|
|
{
|
|
name: "events - Show subscribed Events",
|
|
value: "showSubscribedEvents",
|
|
},
|
|
{
|
|
name: "events - Show published Events",
|
|
value: "showPublishEvents",
|
|
},
|
|
{
|
|
name: "data-hooks - Show subscribed data listeners",
|
|
value: "showSubscribedProps",
|
|
},
|
|
{
|
|
name: "data-changes - Show published data emitters",
|
|
value: "showPublishProps",
|
|
},
|
|
{
|
|
name: "back - Back to the main menu",
|
|
value: "back",
|
|
},
|
|
{
|
|
name: "exit - Exit the programm",
|
|
value: "exit",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "execute - Operations to execute a service of the NoPE-System",
|
|
value: "execute",
|
|
items: [
|
|
{
|
|
name: "repl - switch to console",
|
|
value: "repl",
|
|
},
|
|
{
|
|
name: "execute-service - Execute a service",
|
|
value: "execute-service",
|
|
},
|
|
{
|
|
name: "execute-instance - Execute a service on a instance",
|
|
value: "execute-instance",
|
|
},
|
|
{
|
|
name: "back - Back to the main menu",
|
|
value: "back",
|
|
},
|
|
{
|
|
name: "exit - Exit the programm",
|
|
value: "exit",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "data - Operations to work with data of the NoPE-System",
|
|
value: "data",
|
|
items: [
|
|
{
|
|
name: "show-data - show current data",
|
|
value: "show-data",
|
|
},
|
|
{
|
|
name: "set-available-data - manipulate data for a given path.",
|
|
value: "set-available-data",
|
|
},
|
|
{
|
|
name: "set-data - manipulate data manually",
|
|
value: "set-data",
|
|
},
|
|
{
|
|
name: "back - Back to the main menu",
|
|
value: "back",
|
|
},
|
|
{
|
|
name: "exit - Exit the programm",
|
|
value: "exit",
|
|
},
|
|
],
|
|
},
|
|
{
|
|
name: "exit - Exit the programm",
|
|
value: "exit",
|
|
},
|
|
];
|
|
|
|
let question = choices;
|
|
|
|
let subscriptions: { [index: string]: INopeObserver } = {};
|
|
|
|
while (!exit) {
|
|
const result = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the operation to perform",
|
|
name: "option",
|
|
choices: question,
|
|
},
|
|
])
|
|
).option;
|
|
|
|
switch (result) {
|
|
case "showInstances":
|
|
// Show the Instances
|
|
if (
|
|
dispatcher.instanceManager.instances.data.getContent().length > 0
|
|
) {
|
|
console.log("The following instances are available:");
|
|
|
|
const length =
|
|
dispatcher.instanceManager.instances.data.getContent().length;
|
|
for (const [
|
|
idx,
|
|
instance,
|
|
] of dispatcher.instanceManager.instances.data
|
|
.getContent()
|
|
.sort(dynamicSort("identifier"))
|
|
.entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${
|
|
instance.identifier
|
|
}<${instance.type}>`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No instances has been found.");
|
|
}
|
|
break;
|
|
case "showHosts":
|
|
// Show the hosts:
|
|
console.log("The following host has been found:");
|
|
for (const host of dispatcher.connectivityManager
|
|
.getAllHosts()
|
|
.sort()) {
|
|
console.log(" *\t", host);
|
|
}
|
|
break;
|
|
case "showTypes":
|
|
// Filter the hosts:
|
|
if (
|
|
dispatcher.instanceManager.instances.data.getContent().length > 0
|
|
) {
|
|
const types = new Set<string>();
|
|
const typesAmount: { [index: string]: number } = {};
|
|
dispatcher.instanceManager.instances.data
|
|
.getContent()
|
|
.map((item) => {
|
|
types.add(item.type);
|
|
typesAmount[item.type] = (typesAmount[item.type] || 0) + 1;
|
|
});
|
|
console.log("The following types has been found:");
|
|
for (const [idx, t] of [...types].sort().entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, types.size + 1, true)}.\t<${t}>:${
|
|
typesAmount[t]
|
|
}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No instance available");
|
|
}
|
|
break;
|
|
case "showSubscribedEvents":
|
|
// Filter the hosts:
|
|
if (dispatcher.eventDistributor.emitters.subscribers.length > 0) {
|
|
console.log("The following events are susbcribed:");
|
|
const subscribers =
|
|
dispatcher.eventDistributor.emitters.subscribers;
|
|
const length = subscribers.length;
|
|
for (const [idx, events] of subscribers.sort().entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${events}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No event has been subscribed has been subscribed.");
|
|
}
|
|
break;
|
|
case "showPublishEvents":
|
|
// Filter the hosts:
|
|
if (dispatcher.eventDistributor.emitters.publishers.length > 0) {
|
|
console.log("The following event emitters are registered:");
|
|
const publishers = dispatcher.eventDistributor.emitters.publishers;
|
|
const length = publishers.length;
|
|
for (const [idx, events] of publishers.sort().entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${events}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No known event emitter are registered.");
|
|
}
|
|
break;
|
|
|
|
case "showSubscribedProps":
|
|
// Filter the hosts:
|
|
if (dispatcher.dataDistributor.emitters.subscribers.length > 0) {
|
|
console.log("The following events are susbcribed:");
|
|
const subscribers =
|
|
dispatcher.eventDistributor.emitters.subscribers;
|
|
const length = subscribers.length;
|
|
for (const [idx, events] of subscribers.sort().entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${events}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No known data-hooks are known.");
|
|
}
|
|
break;
|
|
case "showPublishProps":
|
|
// Filter the hosts:
|
|
if (dispatcher.dataDistributor.emitters.publishers.length > 0) {
|
|
console.log("The following event emitters are registered:");
|
|
const publishers = dispatcher.eventDistributor.emitters.publishers;
|
|
const length = publishers.length;
|
|
for (const [idx, events] of publishers.sort().entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${events}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No known publishing properties are registered.");
|
|
}
|
|
break;
|
|
case "showServices":
|
|
if (dispatcher.rpcManager.services.data.getContent().length > 0) {
|
|
console.log("The following services are available:");
|
|
const length =
|
|
dispatcher.rpcManager.services.data.getContent().length;
|
|
for (const [idx, service] of dispatcher.rpcManager.services.data
|
|
.getContent()
|
|
.sort()
|
|
.entries()) {
|
|
console.log(
|
|
` ${padString(idx + 1, length + 1, true)}.\t${service}`
|
|
);
|
|
}
|
|
} else {
|
|
console.log("No services are available.");
|
|
}
|
|
break;
|
|
case "execute-service":
|
|
if (dispatcher.rpcManager.services.data.getContent().length > 0) {
|
|
const service = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the operation service to perform",
|
|
name: "service",
|
|
choices: dispatcher.rpcManager.services.data
|
|
.getContent()
|
|
.sort(),
|
|
},
|
|
])
|
|
).service;
|
|
|
|
await dispatcher.rpcManager.performCall(service, [], {
|
|
timeout: 5000,
|
|
});
|
|
} else {
|
|
console.log("No services are available.");
|
|
}
|
|
|
|
break;
|
|
case "execute-instance":
|
|
if (
|
|
dispatcher.instanceManager.instances.data.getContent().length > 0
|
|
) {
|
|
const instanceName = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the instance",
|
|
name: "instance",
|
|
choices: dispatcher.instanceManager.instances.data
|
|
.getContent()
|
|
.sort(dynamicSort("identifier"))
|
|
.map((item) => {
|
|
return {
|
|
name: item.identifier + "<" + item.type + ">",
|
|
value: item.identifier,
|
|
};
|
|
}),
|
|
},
|
|
])
|
|
).instance;
|
|
const instanceDescription =
|
|
dispatcher.instanceManager.getInstanceDescription(
|
|
instanceName
|
|
) as INopeModuleDescription;
|
|
|
|
const service = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the instance",
|
|
name: "service",
|
|
choices: Object.keys(instanceDescription.functions).sort(),
|
|
},
|
|
])
|
|
).service;
|
|
|
|
try {
|
|
// No Parameters are required
|
|
logger.info("Generating Accessor");
|
|
const instance = await dispatcher.instanceManager.createInstance({
|
|
identifier: instanceDescription.identifier,
|
|
type: instanceDescription.type,
|
|
params: [],
|
|
});
|
|
logger.info("Accessor Generated");
|
|
// Now we know, which service we are trying to call.
|
|
if (
|
|
instanceDescription.functions[service].schema?.inputs?.length ==
|
|
0
|
|
) {
|
|
try {
|
|
// Now we execute the service
|
|
const serviceResult = await instance[service]();
|
|
logger.info("executed: ", instanceName + "." + service);
|
|
logger.info("result=", serviceResult);
|
|
} catch (e) {
|
|
logger.error(
|
|
"Failed to execute ",
|
|
instanceName + "." + service
|
|
);
|
|
logger.error(e);
|
|
}
|
|
} else {
|
|
console.log("parameters are required");
|
|
}
|
|
} catch (e) {
|
|
logger.error("Failed to create an accessor");
|
|
}
|
|
} else {
|
|
console.log("No instance are available.");
|
|
}
|
|
break;
|
|
case "show-data": {
|
|
const data = dispatcher.dataDistributor.pullData("", {});
|
|
const pathes = objectToMap(data);
|
|
|
|
// Filter the hosts:
|
|
if (pathes.size > 0) {
|
|
// Show all Elements / Properties
|
|
const dataPath = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the data to show.",
|
|
name: "dataPath",
|
|
choices: Array.from(pathes.keys()).sort(),
|
|
},
|
|
])
|
|
).dataPath;
|
|
|
|
// Now render the content
|
|
logger.info("content=", pathes.get(dataPath));
|
|
} else {
|
|
logger.warn("No data present.");
|
|
}
|
|
break;
|
|
}
|
|
case "set-available-data": {
|
|
const data = dispatcher.dataDistributor.pullData("", {});
|
|
const pathes = objectToMap(data);
|
|
|
|
if (pathes.size > 0) {
|
|
// Show all Elements / Properties
|
|
const dataPath = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "search-list",
|
|
message: "Select the data to show.",
|
|
name: "dataPath",
|
|
choices: Array.from(pathes.keys()).sort(),
|
|
},
|
|
])
|
|
).dataPath;
|
|
|
|
const data = await getJsonInput();
|
|
|
|
try {
|
|
// Dont gets an update.
|
|
await dispatcher.dataDistributor.pushData(dataPath, data, {
|
|
sender: "nope-cli",
|
|
});
|
|
} catch (e) {
|
|
logger.warn("failed to update the data");
|
|
logger.error(e);
|
|
}
|
|
} else {
|
|
logger.warn("No Preset data available use 'set-data'");
|
|
}
|
|
break;
|
|
}
|
|
case "set-data": {
|
|
// Show all Elements / Properties
|
|
let dataPath = (
|
|
await inquirer.prompt([
|
|
{
|
|
type: "input",
|
|
name: "dataPath",
|
|
message:
|
|
"Enter the data path. Please use '/' as seperator or valid js-notation",
|
|
},
|
|
])
|
|
).dataPath;
|
|
|
|
dataPath = convertPath(dataPath);
|
|
|
|
const data = await getJsonInput();
|
|
|
|
try {
|
|
// Dont gets an update.
|
|
dispatcher.dataDistributor.patternBasedPush(dataPath, data, {
|
|
sender: "nope-cli",
|
|
});
|
|
} catch (e) {
|
|
logger.warn("failed to update the data");
|
|
logger.error(e);
|
|
}
|
|
break;
|
|
}
|
|
case "exit":
|
|
exit = true;
|
|
break;
|
|
case "back":
|
|
question = choices;
|
|
break;
|
|
default:
|
|
for (const choice of choices) {
|
|
if (choice.value === result) {
|
|
question = choice.items || [
|
|
{
|
|
name: "back - Back to the main menu",
|
|
value: "back",
|
|
},
|
|
];
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
process.exit(1);
|
|
} catch (e) {
|
|
logger.error("Something went wrong");
|
|
logger.error(e);
|
|
}
|
|
}
|
|
|
|
// If requested As Main => Perform the Operation.
|
|
if (require.main === module) {
|
|
interact();
|
|
}
|