diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..81211c4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +/node_modules +.vscode \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..30d66cb --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Vyacheslav Sviridov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..ca9221c --- /dev/null +++ b/README.md @@ -0,0 +1,356 @@ +# Cli application based on ENM Topology Browser API + +[![NPM][npm-badge]][npm] +[![LICENSE][license-badge]][license] +[![dependencies][dependencies-badge]][dependencies-david] + +## Main goal + +Main features is type suggestions everywhere where it is possible and detailed attributes and values description available in config mode. + +## Installation + +First you need **node.js** which can be downloaded from official site nodejs.org and installed as described in the docs. + +Next clone or download this repository and run from the project root directory ... + +``` +npm i +``` + +... to install dependencies, + +``` +npm link +``` + +... to add application to your OS $PATH variable if you want to run it from anywhere. +Now you can launch apllication + +``` +tplg-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domian.com +``` + +## Usage + +Recommended environment is Windows Terminal (not _cmd.exe_) or any shell with rich formatting support. After application successfully launched you'll see root content and available commands. + +``` +PS C:\> tplg-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domian.com +✔ Login in... +Authentication Successful +✔ Starting Topology Browser... +✔ Reading Topology... + SubNetwork=ONRM_ROOT_MO> (Use arrow keys or type to search) +> SubNetwork=Core + SubNetwork=LTE + SubNetwork=RBS + SubNetwork=RNC + show + config + up + home + exit +``` + +### Commands + +> **Note:** _only `show` command can have parameter, all other commands haven't._ + +- `show` - shows current object's attributes +- `config` - enters _config_ mode +- `up` - navigate up one level +- `home` - navigate to root folder +- `exit` - logout and exit application + +Start typing and you see only matches commands to your input. + +``` +SubNetwork=ONRM_ROOT_MO> subn +> SubNetwork=Core + SubNetwork=LTE + SubNetwork=RBS + SubNetwork=RNC +SubNetwork=ONRM_ROOT_MO> exi +>exit +``` + +You can navigate to the next level selecting object ... + +``` +SubNetwork=ONRM_ROOT_MO> subn + SubNetwork=Core + SubNetwork=LTE + SubNetwork=RBS +> SubNetwork=RNC +SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC> +> MeContext=RNC01 + MeContext=RNCTEST + MeContext=TEST +``` + +View objects attributes ... + +``` +SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC> show +> show +✔ Reading Topology... + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC + + SubNetworkId: RNC + +SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC> MeContext=RNC01 +> MeContext=RNC01 +✔ Reading Topology... +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> show +> show +✔ Reading Topology... + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01 + + MeContextId: RNC01 + neType: RNC + platformType: CPP +``` + +... show attributes with filter + +``` +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> show type +> show type +✔ Reading Topology... + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01 + + neType: RNC + platformType: CPP + +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> +``` + +Return one level up in FDN tree ... + +``` +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> up +> up +✔ Reading Topology... +SubNetwork=ONRM_ROOT_MO,SubNetwork=RNC> +``` + +Return to the root from anywhere ... + +``` +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> home +> home +✔ Reading Topology... +SubNetwork=ONRM_ROOT_MO> +``` + +And logout and exit ... + +``` +... Network=ONRM_ROOT_MO,SubNetwork=RNC,MeContext=RNC01> exit +> exit +✔ Logout... +Logout OK +PS C:\> +``` + +### Config Mode + +To modify attributes _config_ mode is used. + +Available commands are: + +- `commit` - commiting changes to the network +- `check` - view configuration changes +- `end` - end config mode without commiting +- `exit` - logout and exit application + +``` +... ManagedElement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1> config +> config +✔ Reading Attributes... +✔ Reading Attributes Data... + + syncStatus: SYNCHRONIZED + ipAddress: 10.10.11.1 + managementState: NORMAL + radioAccessTechnology: 4G, 3G + +... lement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(config)# (Use arrow keys or type to search) +> acBarringForCsfb + acBarringForEmergency + acBarringForMoData + acBarringForMoSignalling + ... + commit + check + end + exit +(Move up and down to reveal more choices) +``` + +To modify attribute select it ... + +``` +... lement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(config)# userLabel +... ent=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(userLabel)# + commit + check + end + exit +> get + set + description +``` + +Now you can get it ... + +``` +... ent=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(userLabel)# get +> get + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RBS,MeContext=ERBS01,ManagedElement=1,ENodeBFunction=1,EUtranCellFDD=test1(userLabel) + + userLabel: test1 + + Type: STRING + +``` + +And set it's value ... + +``` +... ent=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(userLabel)# set +? userLabel (STRING): ? test_label +``` + +Check configuration before applying + +``` +... ent=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(userLabel)# check + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RBS,MeContext=ERBS01,ManagedElement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1 + + userLabel: test_label + +``` + +Applying changes to the network ... + +``` +... ent=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(userLabel)# commit + + FDN: SubNetwork=ONRM_ROOT_MO,SubNetwork=RBS,MeContext=ERBS01,ManagedElement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1 + + userLabel: test_label +✔ Commiting Config... +Success +``` + +View attribute's description + +``` +... lement=ERBS01,ENodeBFunction=1,EUtranCellFDD=test1(config)# acBarringForCsfb +... S01,ENodeBFunction=1,EUtranCellFDD=test1(acBarringForCsfb)# description + +acBarringForCsfb: COMPLEX_REF + +DESCRIPTION + Access class barring parameters for mobile originating CSFB calls. +The information is broadcasted in SIB2. + +TRAFFIC DISTURBANCES + Changing this attribute can cause loss of traffic. + +IMMUTABLE + false + +ACTIVE CHOICE CASE + null + + +COMPLEX_REF + AcBarringConfig: AcBarringConfig + + +acBarringTime: INTEGER default: 64 + +DESCRIPTION + Mean access barring time in seconds for mobile originating signalling. + +IMMUTABLE + false + +ACTIVE CHOICE CASE + null + +CONSTRAINTS + Nullable: false + Value Resolution: null + + +acBarringForSpecialAC: LIST default: false,false,false,false,false + +DESCRIPTION + Access class barring for AC 11-15. The first instance in the list is for AC 11, second is for AC 12, and so on. + +IMMUTABLE + false + +ACTIVE CHOICE CASE + null + +CONSTRAINTS + Nullable: false +LISTREFERENCE + BOOLEAN +CONSTRAINTS + Nullable: true + + +acBarringFactor: INTEGER default: 95 + +DESCRIPTION + If the random number drawn by the UE is lower than this value, access is allowed. Otherwise the access is barred. + +IMMUTABLE + false + +ACTIVE CHOICE CASE + null + +CONSTRAINTS + Nullable: false + Value Resolution: null +``` + +## Contribution + +1. [Fork it] +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create new Pull Request + +## Known Issues + +### flickering issue + +In some windows terminal spinner may look flickery. To fix this you need to modify file ./node_modules/ora/index.js + +Add 1 to _clearLine()_ on this line + +```javascript +this.stream.clearLine(1); +``` + +### Issue with type SHORT + +EMN returns error if commited configuration includes attributes with type SHORT. It is not an application issue and should be fixed in API by Ericsson. + +## Credits + +[Contact Me](https://github.com/vvsviridov/) to request new feature or bugs reporting. diff --git a/bin/tplg-cli.js b/bin/tplg-cli.js new file mode 100644 index 0000000..45dd7cc --- /dev/null +++ b/bin/tplg-cli.js @@ -0,0 +1,35 @@ +#!/usr/bin/env node + +const program = require('commander') +const pkg = require('../package.json') + +const TopologyBrowser = require('../lib/TopologyBrowser') +const inputHandler = require('../lib/inputHandler') +const logError = require('../util/logError') + +program + .version(pkg.version) + .requiredOption('-l, --login ', 'ENM User Login') + .requiredOption('-p, --password ', 'ENM User Password') + .requiredOption('-u, --url ', 'ENM Url') + .parse(process.argv) + + +const options = program.opts() + + +async function main() { + try { + const tplg = new TopologyBrowser(options.login, options.password, options.url) + const result = await tplg.login() + const { code } = result + if (code === 'SUCCESS') { + await inputHandler(tplg) + await tplg.logout() + } + } catch (err) { + logError(err) + } +} + +;(async () => main())() diff --git a/lib/TopologyBrowser.js b/lib/TopologyBrowser.js new file mode 100644 index 0000000..ad4964a --- /dev/null +++ b/lib/TopologyBrowser.js @@ -0,0 +1,272 @@ +const axios = require('axios') +const https = require('https') +const axiosCookieJarSupport = require('axios-cookiejar-support').default +const tough = require('tough-cookie') + +const logAttributes = require('../util/logAttributes') +const logAttributeData = require('../util/logAttributeData') +const logDetails = require('../util/logDetails') +const logCommit = require('../util/logCommit') +// const logConfig = require('../util/logConfig') +const requestWrapper = require('../util/requestWrapper') +const banner = require('../util/banner') +const inputByType = require('../lib/inputValue') + +const otherCommands = ['show', 'config', 'up', 'home', 'exit'] +const configCommands = ['commit', 'check', 'end', 'exit'] + +axiosCookieJarSupport(axios) + + +class TopologyBrowser { + constructor(username, password, url) { + this.logoutUrl = `${url}/logout` + this.baseUrl = `${url}/persistentObject/` + this.loginUrl = `${url}/login?IDToken1=${username}&IDToken2=${password}` + + this.currentPoId = 0 + this.nextPoId = 1 + this.childrens = null + this.poIds = [] + this.isConfig = false + this.attributes = null + this.nextVariants = null + this.attributesData = null + this.attribute = null + this.networkDetails = null + this.configSet = [] + this.heartBeatInterval = null + + this.conn = axios.create({ + httpsAgent: new https.Agent({ + rejectUnauthorized: false + }), + withCredentials: true, + jar: new tough.CookieJar(), + }) + this.conn.defaults.jar = new tough.CookieJar() + } + + async login(){ + const response = await requestWrapper(async () => this.conn.post(this.loginUrl), 'Login in...') + console.log(response.data.message.green) + return response.data + } + + async logout(){ + const response = await requestWrapper(async () => this.conn.get(this.logoutUrl), 'Logout...') + if (response.status === 200) { + console.log(`Logout ${response.statusText}`.green) + } else { + console.log(`Logout ${response.statusText}`.red) + } + return response.data + } + + async initialPrompt() { + const response = await requestWrapper(async () => this.conn.get(`${this.baseUrl}network/-1?relativeDepth=0:-2&childDepth=1`, { + withCredentials: true + }), 'Starting Topology Browser...') + const { moType, moName, poId } = response.data.treeNodes[0] + this.currentPoId = poId + this.nextPoId = poId + this.nextVariants = async (input) => await this.nextObjects(input) + return `${moType}=${moName}` + } + + async next(input) { + return await this.nextVariants(input) + } + + async nextObjects(input){ + const filter = input ? input : '' + if (this.currentPoId !== this.nextPoId || !this.childrens) { + this.currentPoId = this.nextPoId + this.poIds.push(this.currentPoId) + const response = await requestWrapper(async () => this.conn.get(`${this.baseUrl}network/${this.currentPoId}`, { + withCredentials: true + })) + if (response.data) { + this.childrens = response.data.treeNodes[0].childrens + } + } + let result = this.childrens + .map(child => `${child.moType}=${child.moName}`) + .concat(otherCommands) + .filter(child => child.toLowerCase().includes(filter.toLowerCase())) + .concat(filter.startsWith('show') ? [filter] : []) + if (result.includes(filter)) { + result = result.filter(item => item !== filter) + result.unshift(filter) + } + return result + } + + async nextAttributes(input) { + const filter = input ? input : '' + let result = this.attributes.map(item => item.key).sort((a, b) => a > b ? 1 : -1) + .concat(configCommands) + .filter(item => item.toLowerCase().includes(filter.toLowerCase())) + .concat(filter.startsWith('set') ? [filter] : []) + if (result.includes(filter)) { + result = result.filter(item => item !== filter) + result.unshift(filter) + } + return result + } + + setIdByCommand(command) { + const nextChild = this.childrens.filter(child => `${child.moType}=${child.moName}` === command)[0] + if (nextChild) { + this.nextPoId = nextChild.poId + return true + } + return false + } + + async show(filter) { + const response = await requestWrapper(async () => this.conn.get(`${this.baseUrl}${this.currentPoId}?includeNonPersistent=false&stringifyLong=true`, { + withCredentials: true + })) + logAttributes(response.data.fdn, response.data.attributes.filter(item => item.key.toLowerCase().includes(filter.toLowerCase()))) + } + + up() { + if (this.poIds.length > 1) { + this.poIds.pop() + this.nextPoId = this.poIds.pop() + this.currentPoId = this.poIds[this.poIds.length - 1] + return true + } + return false + } + + async config(fdn) { + this.isConfig = true + const responseA = await requestWrapper(async () => this.conn.get(`${this.baseUrl}${this.currentPoId}?includeNonPersistent=false&stringifyLong=true`, { + withCredentials: true + }), 'Reading Attributes...') + const { attributes, namespace, namespaceVersion, neType, neVersion, networkDetails, type} = responseA.data + const responseD = await requestWrapper(async () => this.conn.get(`${this.baseUrl}model/${neType}/${neVersion}/${namespace}/${type}/${namespaceVersion}/attributes?includeNonPersistent=false`, { + withCredentials: true + }), 'Reading Attributes Data...') + this.networkDetails = networkDetails + logDetails(networkDetails) + this.attributes = attributes + this.nextVariants = async (input) => await this.nextAttributes(input) + this.attributesData = responseD.data.attributes + return `${fdn}(config)` + } + + end() { + this.isConfig = false + this.attribute = null + this.configSet.length = 0 + this.nextVariants = async (input) => await this.nextObjects(input) + } + + setAttribute(attribute) { + if (!this.attributes) return false + if (!this.attributes.filter(item => item.key === attribute)[0]) return false + if (!this.attribute) { + configCommands.push('get') + configCommands.push('set') + configCommands.push('description') + } + this.attribute = attribute + return true + } + + description() { + const attributeData = this.attributesData.filter(item => item.key === this.attribute)[0] + if (attributeData) { + logAttributeData(attributeData) + if (attributeData.complexRef) { + console.log(`${attributeData.type.magenta} + ${attributeData.complexRef.key.cyan}: ${attributeData.complexRef.description.grey} + `) + attributeData.complexRef.attributes.forEach(attr => logAttributeData(attr)) + } + } else { + console.log('Attribute Not Found!'.yellow) + } + } + + get(fdn) { + const syncStatus = this.networkDetails.filter(item => item.key === 'syncStatus')[0] + if (syncStatus && syncStatus.value === 'UNSYNCHRONIZED') { + console.log(` + ❗ ${syncStatus.key}: ${syncStatus.value} ❗`.yellow) + } + const attribute = this.attributes.filter(item => item.key === this.attribute)[0] + const attributeData = this.attributesData.filter(item => item.key === this.attribute)[0] + logAttributes(fdn, [attribute]) + console.log(` ${'Type: '.green + attributeData['type']} ${attributeData['defaultValue'] ? 'Default: '.yellow + attributeData['defaultValue'] : ''} + `) + if (attributeData.constraints && attributeData.constraints.orderingConstraint) banner(attributeData) + } + + async set() { + const attributeData = this.attributesData.filter(item => item.key === this.attribute)[0] + if (!attributeData) return + if (attributeData.writeBehavior === 'NOT_ALLOWED' || attributeData.immutable) { + console.log('Attribute Is ReadOnly'.yellow) + return + } + if (this.isConfig) { + const found = this.configSet.filter(item => item.key === this.attribute)[0] + const { value } = await inputByType(attributeData) + if (found) { + found.value = value + } else { + this.configSet.push( + { + key: this.attribute, + value, + datatype: attributeData.type, + } + ) + } + } + } + + async commit(fdn) { + logAttributes(fdn, this.configSet) + this.configSet.forEach(item => delete item.from) + const data = { + poId: this.currentPoId, + fdn, + attributes: this.configSet, + } + const config = { + withCredentials: true, + headers: { + 'Content-Type': 'application/json' + }, + } + const response = await requestWrapper(async () => await this.conn.put(`${this.baseUrl}${this.currentPoId}`, data, config), 'Commiting Config...') + if (response && response.data) { + logCommit(response.data) + } else { + console.log('No data or response!'.red) + } + this.configSet.length = 0 + this.end() + return fdn + } + + check(fdn) { + logAttributes(fdn, this.configSet) + // console.log(JSON.stringify(this.configSet, null, 2)) + } + + home() { + this.nextPoId = this.poIds.shift() + this.poIds.length = 0 + this.nextVariants = async (input) => await this.nextObjects(input) + } + +} + + +module.exports = TopologyBrowser \ No newline at end of file diff --git a/lib/inputHandler.js b/lib/inputHandler.js new file mode 100644 index 0000000..22c41bc --- /dev/null +++ b/lib/inputHandler.js @@ -0,0 +1,110 @@ +const inquirer = require('inquirer') +const colors = require('colors') + +const logError = require('../util/logError') +const { isEmpty } = require('../util/validation') + + +inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt')) + + +function buildPrompt(fdn) { + if (fdn.length >= 67) { + return { prefix: '...', prompt: fdn.slice(-65) } + } + return { prefix: '', prompt: fdn } +} + + +function commndUp(tplg, fdn) { + if (tplg.up()) { + fdn = fdn.split(',').slice(0,-1).join(',') + } else { + console.log('There\'s no way up!'.yellow) + } + return fdn +} + + +function commandOther(tplg, fdn, command) { + if (tplg.setIdByCommand(command)) { + fdn = `${fdn},${command}` + } else if (tplg.setAttribute(command)) { + fdn = fdn.replace(/\((\w+)\)/g, `(${command})`) + } else { + console.log('Command Unrecognized❗'.red) + } + return fdn +} + + +async function handleCommand(tplg, fdn, command) { + if (command === 'exit') return + if (command.startsWith('show')) { + const splitted = command.split(/\s(.+)/) + await tplg.show(splitted[1] ? splitted[1].trim() : '') + } else if (command === 'config') { + fdn = await tplg.config(fdn) + } else if (command === 'set') { + await tplg.set() + } else if (command === 'commit') { + fdn = await tplg.commit(fdn.replace(/\((\w+)\)/g, '')) + } else if (command === 'up') { + fdn = commndUp(tplg, fdn) + } else if (command === 'get') { + tplg.get(fdn) + } else if (command === 'check') { + tplg.check(fdn.replace(/\((\w+)\)/g, '')) + } else if (command === 'end') { + tplg.end() + fdn = fdn.replace(/\((\w+)\)/g, '') + } else if (command === 'home') { + tplg.home() + fdn = fdn.split(',', 1)[0] + } else if (command === 'description') { + tplg.description() + } else { + fdn = commandOther(tplg, fdn, command) + } + return fdn +} + + +async function inputHandlerLoop(tplg) { + let prompt = await tplg.initialPrompt() + let fdn = prompt + let prefix = '' + while (true) { + try { + const input = await inquirer.prompt([ + { + type: 'autocomplete', + name: 'command', + message: tplg.isConfig ? prompt.blue.underline : prompt.blue, + pageSize: 10, + prefix: prefix.gray, + suffix: tplg.isConfig ? '#'.blue : '>'.blue, + validate: isEmpty, + source: async (answers, input) => tplg.next(input) + } + ]) + fdn = await handleCommand(tplg, fdn, input.command) + if (!fdn) break + ({ prefix, prompt } = buildPrompt(fdn)) + } catch (err) { + logError(err) + } + } +} + + +async function inputHandler(tplg) { + try { + await inputHandlerLoop(tplg) + } catch (err) { + logError(err) + } +} + + +module.exports = inputHandler \ No newline at end of file diff --git a/lib/inputValue.js b/lib/inputValue.js new file mode 100644 index 0000000..d53ce34 --- /dev/null +++ b/lib/inputValue.js @@ -0,0 +1,156 @@ +const colors = require('colors') +const inquirer = require('inquirer') +const banner = require('../util/banner') +const { isValidNumber, isValidString, checkValueRangeConstraints } = require('../util/validation') + + +async function inputInteger(attributeData) { + const message = `${attributeData.key.yellow} { ${attributeData.unit ? attributeData.unit : 'parrots'.gray} } (${attributeData.type}): ` + const input = await inquirer.prompt([ + { + type: 'input', + name: 'value', + suffix: '?'.green, + message, + default: attributeData.defaultValue, + validate: input => isValidNumber(input, attributeData.constraints), + } + ]) + return +input.value +} + + +async function inputEnumRef(attributeData) { + const message = `Select Value For ${attributeData.key.yellow}: ` + const input = await inquirer.prompt([ + { + type: 'list', + name: 'value', + suffix: '?'.green, + message, + choices: attributeData.enumeration.enumMembers.map(item => ({ name: `${item.key} (${item.description})`, value: item.key, short: item.key })), + default: attributeData.defaultValue, + } + ]) + return input.value +} + + +async function inputBoolean(attributeData) { + const variants = ['true', 'false'] + if (attributeData.constraints.nullable) variants.push('null') + const message = `Select Value For ${attributeData.key.yellow}:` + const input = await inquirer.prompt([ + { + type: 'list', + name: 'value', + suffix: '?'.green, + message, + choices: variants, + default: String(attributeData.defaultValue), + } + ]) + return { + true: true, + false: false, + null: null + }[input.value] +} + + +async function inputString(attributeData) { + const message = `${attributeData.key.yellow} (${attributeData.type}): ` + const input = await inquirer.prompt([ + { + type: 'input', + name: 'value', + suffix: '?'.green, + message, + default: attributeData.defaultValue, + validate: input => isValidString(input, attributeData.constraints), + } + ]) + return input.value +} + + +async function inputList(attributeData) { + const message = `${attributeData.key.yellow} List Of (${attributeData.listReference.type}) Size: ` + const result = [] + const { value } = await inquirer.prompt([ + { + type: 'number', + name: 'value', + suffix: '?'.green, + message, + default: 'null', + validate: input => +input > 0 || input === 'null', + } + ]) + if (value === 'null') return null + const checkResult = checkValueRangeConstraints(value, attributeData.constraints) + if (checkResult) return checkResult + if (attributeData.constraints.orderingConstraint) banner(attributeData) + await inputListValues(attributeData, value, result) + return result.value ? result.value : result +} + + +async function inputListValues(attributeData, listSize, result) { + for (let i = 0; i < listSize; i++) { + let input + if (!attributeData.listReference.key) { + attributeData.listReference.key = `${attributeData.key} [${i}]` + input = await inputByType(attributeData.listReference) + attributeData.listReference.key = null + } else { + input = await inputByType(attributeData.listReference) + } + if (attributeData.constraints.uniqueMembers) { + if (result.indexOf(input.value) !== -1) { + console.log('>>Array Values Should Be Unique'.red) + i-- + continue + } + } + result.push(input.value) + } +} + + +async function inputComplexRef(attributeData) { + const result = [] + const attributes = attributeData.complexRef.attributes.slice() + while (attributes.length > 0) { + const item = attributes.shift() + const { value } = await inputByType(item) + result.push({ + key: item.key, + value, + datatype: item.type + }) + } + return result +} + + +async function inputByType(typeReference) { + const inputs = { + INTEGER: inputInteger, + SHORT: inputInteger, + ENUM_REF: inputEnumRef, + BOOLEAN: inputBoolean, + STRING: inputString, + MO_REF: inputString, + LIST: inputList, + COMPLEX_REF: inputComplexRef + } + if (inputs[typeReference.type]) { + const input = await inputs[typeReference.type](typeReference) + return { value: input } + } + banner(typeReference) +} + + +module.exports = inputByType diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..6bd7995 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,447 @@ +{ + "name": "tplg-cli", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "requires": { + "type-fest": "^0.21.3" + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "axios-cookiejar-support": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/axios-cookiejar-support/-/axios-cookiejar-support-1.0.1.tgz", + "integrity": "sha512-IZJxnAJ99XxiLqNeMOqrPbfR7fRyIfaoSLdPUf4AMQEGkH8URs0ghJK/xtqBsD+KsSr3pKl4DEQjCn834pHMig==", + "requires": { + "is-redirect": "^1.0.0", + "pify": "^5.0.0" + } + }, + "base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" + }, + "bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "requires": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "chalk": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz", + "integrity": "sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==" + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-spinners": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.6.0.tgz", + "integrity": "sha512-t+4/y50K/+4xcCRosKkA7W4gTr1MySvLV0q+PxmG7FJ5g+66ChKurYjxBCjHggHH3HA5Hh9cy+lcUGWDqVH+4Q==" + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==" + }, + "clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colors": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", + "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==" + }, + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==" + }, + "defaults": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", + "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", + "requires": { + "clone": "^1.0.2" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } + }, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "requires": { + "escape-string-regexp": "^1.0.5" + } + }, + "follow-redirects": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.1.tgz", + "integrity": "sha512-HWqDgT7ZEkqRzBvc2s64vSZ/hfOceEol3ac/7tKwzuvEyWx3/4UegXh5oBOIotkGsObyk3xznnSRVADBgWSQVg==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.1.1.tgz", + "integrity": "sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w==", + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.1", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.21", + "mute-stream": "0.0.8", + "ora": "^5.3.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.6", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + } + }, + "inquirer-autocomplete-prompt": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/inquirer-autocomplete-prompt/-/inquirer-autocomplete-prompt-1.4.0.tgz", + "integrity": "sha512-qHgHyJmbULt4hI+kCmwX92MnSxDs/Yhdt4wPA30qnoa01OF6uTXV8yvH4hKXgdaTNmkZ9D01MHjqKYEuJN+ONw==", + "requires": { + "ansi-escapes": "^4.3.1", + "chalk": "^4.0.0", + "figures": "^3.2.0", + "run-async": "^2.4.0", + "rxjs": "^6.6.2" + } + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==" + }, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==" + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "requires": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==" + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==" + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "requires": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "pify": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", + "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==" + }, + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==" + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } + }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "6.6.7", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz", + "integrity": "sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ==", + "requires": { + "tslib": "^1.9.0" + } + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "requires": { + "ansi-regex": "^5.0.0" + } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "requires": { + "os-tmpdir": "~1.0.2" + } + }, + "tough-cookie": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.0.0.tgz", + "integrity": "sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==", + "requires": { + "psl": "^1.1.33", + "punycode": "^2.1.1", + "universalify": "^0.1.2" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==" + }, + "universalify": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", + "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "requires": { + "defaults": "^1.0.3" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..cac3f58 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "tplg-cli", + "version": "1.0.0a", + "description": "This is a cli application to access Ericcson Network Manager (ENM) Topology Browser", + "bin": "./bin/tplg-cli.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "vvsviridov", + "license": "MIT", + "dependencies": { + "axios": "^0.21.1", + "axios-cookiejar-support": "^1.0.1", + "colors": "^1.4.0", + "commander": "^7.2.0", + "inquirer": "^8.1.1", + "inquirer-autocomplete-prompt": "^1.4.0", + "ora": "^5.4.1", + "tough-cookie": "^4.0.0" + } +} diff --git a/util/banner.js b/util/banner.js new file mode 100644 index 0000000..ddbaa58 --- /dev/null +++ b/util/banner.js @@ -0,0 +1,13 @@ +const colors = require('colors') + + +function banner(params) { + console.log(` + If you see this, pls, send me this message: + ${JSON.stringify(params, null, 2)} + If you see this, pls, send me this message: + `) +} + + +module.exports = banner \ No newline at end of file diff --git a/util/logAttributeData.js b/util/logAttributeData.js new file mode 100644 index 0000000..80816fb --- /dev/null +++ b/util/logAttributeData.js @@ -0,0 +1,83 @@ +const colors = require('colors') + +function logDefaultValue(value) { + return value ? ` default: ${value}` : '' +} + + +function logAttribute(key, attribute, output) { + let attrName = key.replace(/([A-Z])/g, ' $1') + if (attribute !== undefined && attribute !== '') { + output.push(`${attrName.toLocaleUpperCase().blue} + ${attribute} + `) + } +} + + +function logConstraints(constraints, output) { + output.push(`${Object.keys({constraints}).pop().toLocaleUpperCase().blue}`) + if (constraints.valueRangeConstraints) { + if (constraints.valueRangeConstraints.minValue !== undefined && constraints.valueRangeConstraints.maxValue !== undefined) { + output.push(` Range: ${constraints.valueRangeConstraints.minValue}..${constraints.valueRangeConstraints.maxValue}`) + } + } + ['nullable', 'validContentRegex', 'valueResolution'].forEach(key => { + if (Object.keys(constraints).includes(key)) { + output.push(` ${key.replace(/([A-Z])/g, ' $1').replace(/^([a-z])/g, (l) => l.toUpperCase()).yellow}: ${constraints[key]}`) + } + }) +} + + +function logEnumeration(enumeration, output) { + output.push(`${Object.keys({enumeration}).pop().toLocaleUpperCase().blue} + ${enumeration.key.cyan} + ${enumeration.description}`) + enumeration.enumMembers.forEach(item => output.push(` ${item.key.yellow} (${item.value}): -- ${item.description.gray}`)) +} + + +function logList(listReference, output) { + output.push(`${Object.keys({listReference}).pop().toLocaleUpperCase().blue} + ${listReference.type}`) + if (listReference.constraints){ + logConstraints(listReference.constraints, output) + } +} + + +function logAttributeData(attributeData) { + const attributeDataKeys = [ + 'key', + 'type', + 'defaultValue', + 'description', + 'trafficDisturbances', + 'unit', + 'multiplicationFactor', + 'immutable', + 'precondition', + 'dependencies', + 'sideEffects', + 'activeChoiceCase', + ] + + const output = [` +${attributeData['key'].yellow.bold}: ${attributeData['type'].green} ${logDefaultValue(attributeData['defaultValue'])} + `] + attributeDataKeys.slice(3).forEach((key) => logAttribute(key, attributeData[key], output)) + if (attributeData.constraints) { + logConstraints(attributeData.constraints, output) + } + if (attributeData.enumeration) { + logEnumeration(attributeData.enumeration, output) + } + if (attributeData.listReference) { + logList(attributeData.listReference, output) + } + console.log(output.join('\n') + '\n') +} + + +module.exports = logAttributeData \ No newline at end of file diff --git a/util/logAttributes.js b/util/logAttributes.js new file mode 100644 index 0000000..d4f4cfd --- /dev/null +++ b/util/logAttributes.js @@ -0,0 +1,36 @@ +const colors = require('colors') + + +function transformAttributes(element) { + if (Array.isArray(element)) { + return element.map(item => transformAttributes(item)) + } + if (Array.isArray(element.value)) { + return { [element.key]: transformAttributes(element.value) } + } + return element.key ? { [element.key]: element.value } : element +} + + +function colorize(attributes) { + const sorted = attributes.sort ? attributes.sort((a, b) => a.key < b.key ? -1 : 1) : attributes + return JSON.stringify(transformAttributes(sorted), null, 1) + .replace(/["(){}\[\]]/mg, '') + .replace(/^\s*,*\n/mg, '') + .replace(/,$/mg, '') + .replace(/^(\s{2}\w+):/mg, '$1:'.green) + .replace(/^(\s{4}\w+):/mg, '$1:'.yellow) + .replace(/^(\s{5}\w+):/mg, '$1:'.cyan) +} + + +function logAttributes(fdn, attributes) { + // console.log(JSON.stringify(attributes, null, 2)) + const output = ` + ${'FDN'.yellow.bold}: ${fdn.bold} + +${colorize(attributes)}` + console.log(output) +} + +module.exports = logAttributes \ No newline at end of file diff --git a/util/logCommit.js b/util/logCommit.js new file mode 100644 index 0000000..cafaa80 --- /dev/null +++ b/util/logCommit.js @@ -0,0 +1,10 @@ +const colors = require('colors') + + +function logCommit(commitResult) { + if (commitResult.title === 'Success') { + console.log(commitResult.title.green) + } +} + +module.exports = logCommit \ No newline at end of file diff --git a/util/logConfig.js b/util/logConfig.js new file mode 100644 index 0000000..0f193f2 --- /dev/null +++ b/util/logConfig.js @@ -0,0 +1,11 @@ +const colors = require('colors') + + +function logConfig(configSet) { + const output = configSet.map(config => ` ${config.key.yellow}: ${config.from}->${config.value} ${config.datatype.grey}`) + console.log(` +${output.join('\n')} + `) +} + +module.exports = logConfig \ No newline at end of file diff --git a/util/logDetails.js b/util/logDetails.js new file mode 100644 index 0000000..68a72cb --- /dev/null +++ b/util/logDetails.js @@ -0,0 +1,11 @@ +const colors = require('colors') + + +function logDetails(networkDetails) { + const output = networkDetails.map(details => ` ${details.key.gray}: ${details.value === 'UNSYNCHRONIZED' ? details.value.yellow: details.value.gray}`) + console.log(` +${output.join('\n')} + `) +} + +module.exports = logDetails \ No newline at end of file diff --git a/util/logError.js b/util/logError.js new file mode 100644 index 0000000..cda6755 --- /dev/null +++ b/util/logError.js @@ -0,0 +1,16 @@ +const colors = require('colors') + + +function logError(err) { + if (err.response && err.response.data && err.response.data.message){ + console.error(err.response.status.yellow, err.response.data.message.red) + } else if (err.response && err.response.data && err.response.data.title) { + console.log(`${err.response.data.title.red.bold}: ${err.response.data.body.yellow}`) + console.log(`Error Code ${err.response.data.errorCode}: ${err.response.data.detail.gray}`) + } else { + console.error(err) + } +} + + +module.exports = logError \ No newline at end of file diff --git a/util/requestWrapper.js b/util/requestWrapper.js new file mode 100644 index 0000000..d650a66 --- /dev/null +++ b/util/requestWrapper.js @@ -0,0 +1,21 @@ +const ora = require('ora') +const logError = require('./logError') + + +async function requestWrapper(func, text = 'Reading Topology...') { + const spinner = ora(text) + let result + try { + spinner.start() + result = await func() + spinner.succeed() + } catch (err) { + spinner.fail() + logError(err) + } finally { + return result + } +} + + +module.exports = requestWrapper \ No newline at end of file diff --git a/util/validation.js b/util/validation.js new file mode 100644 index 0000000..786a04d --- /dev/null +++ b/util/validation.js @@ -0,0 +1,38 @@ +const isEmpty = input => (input === '' ? 'Empty Inputs not Allowed'.red : true) + +const isValidNumber = (input, constraints) => { + if (constraints) { + const test = input ? input : '' + if (!constraints.nullable && test === 'null') return 'Value Can\'t Be a null'.red + if (constraints.nullable && test === 'null') return true + if (!test.toString().match(/-?[0-9]+/)) return 'Input Is Not a Number'.red + const checkResult = checkValueRangeConstraints(+test, constraints) + if (checkResult) return checkResult + } + return true +} + +const isValidString = (input, constraints) => { + if (constraints) { + const test = input ? input : '' + if (!constraints.nullable && test === 'null') return 'Value Can\'t Be a null'.red + if (constraints.nullable && test === 'null') return true + if (constraints.validContentRegex && !test.match(constraints.validContentRegex)) return 'Input Doesn\'t Match RegEx'.red + const checkResult = checkValueRangeConstraints(test.length, constraints) + if (checkResult) return checkResult + } + return true +} + +const checkValueRangeConstraints = (value, constraints) => { + if (constraints.valueRangeConstraints) { + const inRange = constraints.valueRangeConstraints.some(item => { + const min = item.minValue ? item.minValue <= value : true + const max = item.maxValue ? item.maxValue >= value : true + return min && max + }) + if (!inRange) return 'Input is Outside Allowed Range'.red + } +} + +module.exports = { isEmpty, isValidNumber, isValidString, checkValueRangeConstraints} \ No newline at end of file