first alpha

This commit is contained in:
Vyacheslav.Sviridov
2022-06-09 20:52:09 +06:00
parent 8b3e8ae5c8
commit fc41ae955f
54 changed files with 2416 additions and 1976 deletions

40
LICENSE Normal file → Executable file
View File

@@ -1,21 +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
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.

388
README.md Normal file → Executable file
View File

@@ -1,194 +1,194 @@
# Cli application based on ENM AutoProvisioning API
[![Github version](https://img.shields.io/github/package-json/version/vvsviridov/prvn-cli?label=prvn-cli&color=brightgreen&logo=github)](https://github.com/vvsviridov/prvn-cli)
[![Npm version](https://img.shields.io/npm/v/prvn-cli?color=red&logo=npm&label=prvn-cli)](https://www.npmjs.com/package/prvn-cli)
## Main goal
A simple CLI interface for AutoProvisioning API.
## Installation
First you need **node.js** which can be downloaded from official site [nodejs.org](https://nodejs.org/en/download/) and installed as described in the docs.
Then you can run directly from NPM without installation
```
npx prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
```
Or install with NPM
```
npm i prvn-cli
```
Or download this repository and run from the project root directory ...
```
npm install
```
... 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
```
prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
```
## Usage
Recommended environment is Windows Terminal (not _cmd.exe_) or any shell with rich formatting support. After application successfully launched youll see root content and available commands.
### Help Page
```
> prvn-cli --help
Usage: prvn-cli [options]
Options:
-V, --version output the version number
-l, --login <letters> ENM User Login
-p, --password <letters> ENM User Password
-u, --url <letters> ENM Url
-h, --help display help for command
```
### Connection
```
>prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
✔ Login in...
✔ Getting projects...
323 projects> (Use arrow keys or type to search)
──────────────
> [new]
[exit]
──────────────
Project1 (1) 1✅
Project2 (2) 1✅ 1⌛
Project3 (2) 1❌ 1⌚
Project4 (1) 1❌
```
### Working with Projects
- `[new]` - Import an Auto Provisioning project to start Auto Provisioning workflows based on the content of the AutoProvisioning project. The Auto Provisioning project file contains project related data in the projectInfo.xml file and node folders which contain configurations required to execute AutoProvisioning use-cases.
- `[exit]` - Exit this app.
Start typing and you see only commands and projects matches input.
```
323 projects> pro
──────────────
──────────────
> Project1 (1) 1✅
Project2 (2) 1✅ 1⌛
Project3 (2) 1❌ 1⌚
Project4 (1) 1❌
SubNetwork=ONRM_ROOT_MO> ex
──────────────
>[exit]
──────────────
```
### Working with Single Project
Select project you want to work with.
Available commands are:
- `[delete]` - Delete of an Auto Provisioning project removes an Auto Provisioning project and all the Auto Provisioning data for nodes within that project. This includes removal or rollback of any ongoing Auto Provisioning workflows within that project.
- `[back]` - Return to projects.
- `[exit]` - Exit this app.
```
323 projects> Project1 (1) 1✅
✔ Getting Project1s status...
✔ Getting Project1s properties...
Project id : Project1
Author : Ericsson
Creation Date : 2019-01-06 11:23:39
Description : Project1 description
Nodes :
RadioNode1
RadioNode
3432-762-238
192.168.192.168
Successful
Integration Completed
Project1> (Use arrow keys or type to search)
──────────────
> [delete]
[back]
[exit]
──────────────
RadioNode1
```
### Wotking with Node
Select node ...
```
Project1 (RadioNode1) > (Use arrow keys or type to search)
──────────────
> [status]
[properties]
[delete]
[bind]
[cancel]
[resume]
[configurations]
[siteinstall]
[back]
(Move up and down to reveal more choices)
```
Available commands are:
- `[status]` - Retrieving Auto Provisioning node status returns the node status information for each task that has been executed for the specified node.
- `[properties]` - Retrieving Auto Provisioning node properties returns the node properties for each task that has been executed for the specified node.
- `[delete]` - Delete an Auto Provisioning node removes the Auto Provisioning data for a Network Element. If a node is the last node in a project and there are no profiles associated with the project the project will automatically be deleted.
- `[bind]` - Binding a hardware serial number to a node configuration associates the specified node configurations with a hardware serial number for Zero Touch Integration or Hardware Replace.
- `[cancel]` - Cancelling the auto provisioning activity rolls back an AutoProvisoning workflow for Node Integration. For expansion a node is rolled back to it\s original configuration if additional configurations have been applied to the node.
- `[resume]` - Resuming the auto provisioning activity recommences an Auto Provisioning workflow that is suspended.
- `[configurations]` - Uploading an auto provisioning configuration replaces a configuration file that was part of the initial Auto Provisioning node configuration.
- `[siteinstall]` - Download Site Installation File (SIF) that is required to be taken on site for LMT Integration or LMT Hardware Replace.
- `[back]` - Return to project\s nodes.
- `[exit]` - Exit this app.
## 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);
```
## Credits
[Contact Me](https://github.com/vvsviridov/) to request new feature or bugs reporting.
## Changes
1.0.0 - is released
# Cli application based on ENM AutoProvisioning API
[![Github version](https://img.shields.io/github/package-json/version/vvsviridov/prvn-cli?label=prvn-cli&color=brightgreen&logo=github)](https://github.com/vvsviridov/prvn-cli)
[![Npm version](https://img.shields.io/npm/v/prvn-cli?color=red&logo=npm&label=prvn-cli)](https://www.npmjs.com/package/prvn-cli)
## Main goal
A simple CLI interface for AutoProvisioning API.
## Installation
First you need **node.js** which can be downloaded from official site [nodejs.org](https://nodejs.org/en/download/) and installed as described in the docs.
Then you can run directly from NPM without installation
```
npx prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
```
Or install with NPM
```
npm i prvn-cli
```
Or download this repository and run from the project root directory ...
```
npm install
```
... 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
```
prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
```
## Usage
Recommended environment is Windows Terminal (not _cmd.exe_) or any shell with rich formatting support. After application successfully launched youll see root content and available commands.
### Help Page
```
> prvn-cli --help
Usage: prvn-cli [options]
Options:
-V, --version output the version number
-l, --login <letters> ENM User Login
-p, --password <letters> ENM User Password
-u, --url <letters> ENM Url
-h, --help display help for command
```
### Connection
```
>prvn-cli -l USERNAME -p PASSWORD -u https://enm.your.company.domain.com
✔ Login in...
✔ Getting projects...
323 projects> (Use arrow keys or type to search)
──────────────
> [new]
[exit]
──────────────
Project1 (1) 1✅
Project2 (2) 1✅ 1⌛
Project3 (2) 1❌ 1⌚
Project4 (1) 1❌
```
### Working with Projects
- `[new]` - Import an Auto Provisioning project to start Auto Provisioning workflows based on the content of the AutoProvisioning project. The Auto Provisioning project file contains project related data in the projectInfo.xml file and node folders which contain configurations required to execute AutoProvisioning use-cases.
- `[exit]` - Exit this app.
Start typing and you see only commands and projects matches input.
```
323 projects> pro
──────────────
──────────────
> Project1 (1) 1✅
Project2 (2) 1✅ 1⌛
Project3 (2) 1❌ 1⌚
Project4 (1) 1❌
SubNetwork=ONRM_ROOT_MO> ex
──────────────
>[exit]
──────────────
```
### Working with Single Project
Select project you want to work with.
Available commands are:
- `[delete]` - Delete of an Auto Provisioning project removes an Auto Provisioning project and all the Auto Provisioning data for nodes within that project. This includes removal or rollback of any ongoing Auto Provisioning workflows within that project.
- `[back]` - Return to projects.
- `[exit]` - Exit this app.
```
323 projects> Project1 (1) 1✅
✔ Getting Project1s status...
✔ Getting Project1s properties...
Project id : Project1
Author : Ericsson
Creation Date : 2019-01-06 11:23:39
Description : Project1 description
Nodes :
RadioNode1
RadioNode
3432-762-238
192.168.192.168
Successful
Integration Completed
Project1> (Use arrow keys or type to search)
──────────────
> [delete]
[back]
[exit]
──────────────
RadioNode1
```
### Wotking with Node
Select node ...
```
Project1 (RadioNode1) > (Use arrow keys or type to search)
──────────────
> [status]
[properties]
[delete]
[bind]
[cancel]
[resume]
[configurations]
[siteinstall]
[back]
(Move up and down to reveal more choices)
```
Available commands are:
- `[status]` - Retrieving Auto Provisioning node status returns the node status information for each task that has been executed for the specified node.
- `[properties]` - Retrieving Auto Provisioning node properties returns the node properties for each task that has been executed for the specified node.
- `[delete]` - Delete an Auto Provisioning node removes the Auto Provisioning data for a Network Element. If a node is the last node in a project and there are no profiles associated with the project the project will automatically be deleted.
- `[bind]` - Binding a hardware serial number to a node configuration associates the specified node configurations with a hardware serial number for Zero Touch Integration or Hardware Replace.
- `[cancel]` - Cancelling the auto provisioning activity rolls back an AutoProvisoning workflow for Node Integration. For expansion a node is rolled back to it\s original configuration if additional configurations have been applied to the node.
- `[resume]` - Resuming the auto provisioning activity recommences an Auto Provisioning workflow that is suspended.
- `[configurations]` - Uploading an auto provisioning configuration replaces a configuration file that was part of the initial Auto Provisioning node configuration.
- `[siteinstall]` - Download Site Installation File (SIF) that is required to be taken on site for LMT Integration or LMT Hardware Replace.
- `[back]` - Return to project\s nodes.
- `[exit]` - Exit this app.
## 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);
```
## Credits
[Contact Me](https://github.com/vvsviridov/) to request new feature or bugs reporting.
## Changes
1.0.0 - is released

210
bin/enm-cli.js Normal file → Executable file
View File

@@ -1,98 +1,112 @@
#!/usr/bin/env node
const { Command, Option } = require('commander')
const pkg = require('../package.json')
const inquirer = require('inquirer')
const { isEmpty } = require('../util/validation')
require('dotenv').config()
const AutoProvisioning = require('../lib/components/AutoProvisioning/AutoProvisioning')
const TopologyBrowser = require('../lib/components/TopologyBrowser/TopologyBrowser')
const logError = require('../util/logError')
const applications = ['tplg', 'prvn']
const program = new Command()
program
.version(pkg.version)
// .option('-l, --login <letters>', 'ENM User Login')
// .option('-p, --password <letters>', 'ENM User Password')
.addOption(new Option('-l, --login <letters>', 'ENM User Login').env('LOGIN'))
.addOption(new Option('-p, --password <letters>', 'ENM User Password').env('PASSWORD'))
.addOption(new Option('-a, --application <letters>', 'Start specified application')
.choices(applications)
.default('tplg')
)
.requiredOption('-u, --url <letters>', 'ENM Url')
.parse(process.argv)
const options = program.opts()
async function promptUsername() {
const input = await inquirer.prompt([
{
type: 'input',
name: 'value',
suffix: chalk.bgGreen('?'),
message: 'Type ENM login',
validate: isEmpty,
}
])
return input.value
}
async function promptPassword() {
const input = await inquirer.prompt([
{
type: 'password',
name: 'value',
message: `Type ${options.login}'s ENM password`,
validate: isEmpty,
}
])
return input.value
}
async function selectApplication() {
let selectedApp
if (options.application && options.application in applications) {
selectedApp = options.application
} else {
const input = await inquirer.prompt([
{
type: 'list',
name: 'application',
suffix: '💾',
message,
choices: applications
}])
selectedApp = input.application
}
return {
tplg: TopologyBrowser,
prvn: AutoProvisioning
}[selectedApp]
}
async function main() {
try {
const app = new selectApplication()(options.login || await promptUsername(), options.password || await promptPassword(), options.url)
const result = await app.login()
const { code } = result
if (code === 'SUCCESS') {
await app.inputHandler()
await app.logout()
}
} catch (error) {
logError(error)
}
}
; (async () => await main())()
#!/usr/bin/env node
const { Command, Option } = require('commander')
const pkg = require('../package.json')
const inquirer = require('inquirer')
const path = require('path')
const { isEmpty } = require('../util/validation')
require('dotenv').config({ path: [...__dirname.split(path.sep).slice(0,-1), '.env'].join(path.sep) })
const AutoProvisioning = require('../lib/applications/AutoProvisioning/AutoProvisioning')
const TopologyBrowser = require('../lib/applications/TopologyBrowser/TopologyBrowser')
const logError = require('../util/logError')
if (process.env.NODE_ENV === 'development') {
process.on('uncaughtException', function (exception) {
console.log(exception);
})
}
const applications = [
{
id: 'tplg',
appClass: TopologyBrowser,
name: 'Topology Browser',
},
{
id: 'prvn',
appClass: AutoProvisioning,
name: 'Auto Provisioning',
}
]
const appIds = applications.map(item => item.id)
const program = new Command()
program
.version(pkg.version)
.addOption(new Option('-l, --login <letters>', 'ENM User Login').env('LOGIN'))
.addOption(new Option('-p, --password <letters>', 'ENM User Password').env('PASSWORD'))
.addOption(new Option('-a, --application <letters>', 'Start specified application')
.choices(appIds)
)
.requiredOption('-u, --url <valid URL>', 'ENM Url')
.parse(process.argv)
const options = program.opts()
async function promptUsername() {
const input = await inquirer.prompt([
{
type: 'input',
name: 'value',
prefix: '👤',
message: 'Type ENM login:',
validate: isEmpty,
}
])
return input.value
}
async function promptPassword() {
const input = await inquirer.prompt([
{
type: 'password',
name: 'value',
prefix: '🔑',
message: 'Type ENM password:',
validate: isEmpty,
}
])
return input.value
}
async function selectApplication() {
let selectedApp
if (options.application && appIds.includes(options.application)) {
selectedApp = options.application
} else {
const input = await inquirer.prompt([
{
type: 'list',
name: 'application',
prefix: '💾',
message: 'Select Application:',
choices: applications.map(item => ({name: item.name, value: item.id, short: item.id}))
}])
selectedApp = input.application
}
const { appClass } = applications.find(item => item.id === selectedApp)
const login = options.login || await promptUsername()
const password = options.password || await promptPassword()
return new appClass(login, password, options.url)
}
async function main() {
try {
const app = await selectApplication()
await app.login()
await app.inputHandler()
await app.logout()
} catch (error) {
logError(error)
}
}
;(async () => await main())()

View File

@@ -1,90 +1,89 @@
const ENM = require('../ENM/ENM')
const { getProjects, getProjectData, deleteProject, newProject } = require('./projects')
const {
getNode,
getNodeStatus,
getNodeProperties,
bindNode,
cancelNode,
resumeNode,
configurationsNode,
siteinstallNode
} = require('./nodes')
const inputHandler = require('./inputHandler')
const createNext = require('../../../util/createNext')
class AutoProvisioning extends ENM {
constructor(username, password, url) {
super(username, password, url)
this.appUrl = '/auto-provisioning/v1'
this.projects = null
this.projectIndex = -1
this.nodes = null
this.nodeIndex = -1
this.commands = null
this.choices = null
this.help = 'No results...'
}
async getProjects() {
return await getProjects.call(this)
}
async getProjectData() {
return await getProjectData.call(this)
}
async newProject() {
await newProject.call(this)
}
async deleteProject() {
await deleteProject.call(this)
}
async getNode() {
return await getNode.call(this)
}
async getNodeStatus() {
await getNodeStatus.call(this)
}
async getNodeProperties() {
await getNodeProperties.call(this)
}
async bindNode() {
await bindNode.call(this)
}
async cancelNode() {
await cancelNode.call(this)
}
async resumeNode() {
await resumeNode.call(this)
}
async configurationsNode() {
await configurationsNode.call(this)
}
async siteinstallNode() {
await siteinstallNode.call(this)
}
async next(input) {
return createNext.call(this, input ? input : '')
}
async inputHandler() {
await inputHandler.call(this)
}
}
const ENM = require('../../components/ENM')
const { getProjects, getProjectData, deleteProject, newProject } = require('./projects')
const {
getNode,
getNodeStatus,
getNodeProperties,
bindNode,
cancelNode,
resumeNode,
configurationsNode,
siteinstallNode
} = require('./nodes')
const inputHandler = require('./inputHandler')
const createNext = require('../../../util/createNext')
class AutoProvisioning extends ENM {
constructor(username, password, url) {
super(username, password, url)
this.appUrl = '/auto-provisioning/v1'
this.projects = null
this.projectIndex = -1
this.nodes = null
this.nodeIndex = -1
this.prompt = ''
this.help = 'No results...'
}
async getProjects() {
return await getProjects.call(this)
}
async getProjectData() {
return await getProjectData.call(this)
}
async newProject() {
await newProject.call(this)
}
async deleteProject() {
await deleteProject.call(this)
}
async getNode() {
return await getNode.call(this)
}
async getNodeStatus() {
await getNodeStatus.call(this)
}
async getNodeProperties() {
await getNodeProperties.call(this)
}
async bindNode() {
await bindNode.call(this)
}
async cancelNode() {
await cancelNode.call(this)
}
async resumeNode() {
await resumeNode.call(this)
}
async configurationsNode() {
await configurationsNode.call(this)
}
async siteinstallNode() {
await siteinstallNode.call(this)
}
async next(input) {
return createNext.call(this, input ? input : '')
}
async inputHandler() {
await inputHandler.call(this)
}
}
module.exports = AutoProvisioning

View File

@@ -1,107 +1,96 @@
const inquirer = require('inquirer')
const chalk = require('chalk')
const logError = require('../../../util/logError')
const { isEmpty } = require('../../../util/validation')
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
async function commandOther(prvn, prompt, command) {
const choosedIndex = prvn.choices.indexOf(command)
if (choosedIndex !== -1) {
if (prvn.nodes) {
prvn.nodeIndex = choosedIndex
prompt = prvn.getNode()
} else {
prvn.projectIndex = choosedIndex
prompt = await prvn.getProjectData()
}
}
return prompt
}
async function handleCommand(prvn, prompt, command) {
const [, cmd] = command.match(/\[(\w+)\]/) || [, command]
switch (cmd) {
case 'exit':
return
case 'new':
await prvn.newProject()
break
case 'back':
prompt = prvn.nodeIndex ? await prvn.getProjects() : await prvn.getProjectData()
break
case 'delete':
prvn.nodeIndex ? prvn.deleteNode() : await prvn.deleteProject()
break
case 'status':
await prvn.getNodeStatus()
break
case 'properties':
await prvn.getNodeProperties()
break
case 'bind':
await prvn.bindNode()
break
case 'cancel':
await prvn.cancelNode()
break
case 'resume':
await prvn.resumeNode()
break
case 'configurations':
await prvn.configurationsNode()
break
case 'siteinstall':
await prvn.siteinstallNode()
break
default:
prompt = await commandOther(prvn, prompt, command)
}
return prompt
}
async function inputHandlerLoop(prvn) {
let prompt = await prvn.getProjects()
let prefix = ''
while (true) {
try {
const input = await inquirer.prompt([
{
type: 'autocomplete',
name: 'command',
message: chalk.bold.blue(prompt),
pageSize: 10,
prefix: chalk.bold.grey(prefix),
suffix: chalk.bold.blue('>'),
validate: isEmpty,
source: async (answers, input) => await prvn.next(input),
emptyText: prvn.help,
}
])
prompt = await handleCommand(prvn, prompt, input.command)
if (!prompt) break
} catch (error) {
logError(error)
}
}
}
async function inputHandler() {
try {
await inputHandlerLoop(this)
} catch (error) {
logError(error)
}
}
const inquirer = require('inquirer')
const chalk = require('chalk')
const logError = require('../../../util/logError')
const { isEmpty } = require('../../../util/validation')
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
async function commandOther(prvn, command) {
const choosedIndex = prvn.choices.indexOf(command)
if (choosedIndex !== -1) {
if (prvn.nodes) {
prvn.nodeIndex = choosedIndex
prvn.getNode()
} else {
prvn.projectIndex = choosedIndex
await prvn.getProjectData()
}
}
}
async function handleCommand(prvn, command) {
const [, cmd] = command.match(/\[(\w+)\]/) || [, command]
switch (cmd) {
case 'exit':
prvn.prompt = ''
break
case 'new':
await prvn.newProject()
break
case 'back':
prvn.nodeIndex ? await prvn.getProjects() : await prvn.getProjectData()
break
case 'delete':
prvn.nodeIndex ? prvn.deleteNode() : await prvn.deleteProject()
break
case 'status':
await prvn.getNodeStatus()
break
case 'properties':
await prvn.getNodeProperties()
break
case 'bind':
await prvn.bindNode()
break
case 'cancel':
await prvn.cancelNode()
break
case 'resume':
await prvn.resumeNode()
break
case 'configurations':
await prvn.configurationsNode()
break
case 'siteinstall':
await prvn.siteinstallNode()
break
default:
await commandOther(prvn, command)
}
}
async function inputHandler() {
await this.getProjects()
while (true) {
try {
const input = await inquirer.prompt([
{
type: 'autocomplete',
name: 'command',
message: chalk.bold.blue(this.prompt),
pageSize: 10,
prefix: '',
suffix: chalk.bold.blue('>'),
validate: isEmpty,
source: async (answers, input) => await this.next(input),
emptyText: this.help,
}
])
await handleCommand(this, input.command)
if (!this.prompt) break
} catch (error) {
logError(error)
}
}
}
module.exports = inputHandler

View File

@@ -1,165 +1,165 @@
const fs = require('fs')
const fsPromises = require('fs').promises
const path = require('path')
const FormData = require('form-data')
const chalk = require('chalk')
const inquirer = require('inquirer')
const { logNodeStatus, logNodeProperties } = require('../../../util/logNode')
const { isValidHardwareId } = require('../../../util/validation')
const nodeCommands = [
'status', 'properties', 'delete',
'bind', 'cancel', 'resume',
'configurations', 'siteinstall', 'back',
'exit'
]
const nodeCommandsHelp = [
'[status] - Retrieving Auto Provisioning node status returns the node status information for each task that has been executed for the specified node.',
'[properties] - Retrieving Auto Provisioning node properties returns the node properties for each task that has been executed for the specified node.',
'[delete] - Delete an Auto Provisioning node removes the Auto Provisioning data for a Network Element. If a node is the last node in a project and there are no profiles associated with the project the project will automatically be deleted.',
'[bind] - Binding a hardware serial number to a node configuration associates the specified node configurations with a hardware serial number for Zero Touch Integration or Hardware Replace.',
'[cancel] - Cancelling the auto provisioning activity rolls back an AutoProvisoning workflow for Node Integration. For expansion a node is rolled back to it\'s original configuration if additional configurations have been applied to the node.',
'[resume] - Resuming the auto provisioning activity recommences an Auto Provisioning workflow that is suspended.',
'[configurations] - Uploading an auto provisioning configuration replaces a configuration file that was part of the initial Auto Provisioning node configuration.',
'[siteinstall] - Download Site Installation File (SIF) that is required to be taken on site for LMT Integration or LMT Hardware Replace.',
'[back] - Return to project\'s nodes.',
'[exit] - Exit this app.',
]
async function getNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
this.commands = nodeCommands
this.help = nodeCommandsHelp.join('\n ')
this.choices = [] // this.nodes.filter(item => item !== nodeId)
return `${projectId} (${nodeId}) `
}
async function getNodeStatus() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}`
}
const { data: { statusEntries } } = await this.httpClient.request(axiosConfig)
logNodeStatus(statusEntries)
}
async function getNodeProperties() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s properties...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}?filter=properties`
}
const { data: { attributes, attributeGroups } } = await this.httpClient.request(axiosConfig)
logNodeProperties(attributes, attributeGroups)
}
async function bindNode() {
const hardwareId = await inquirer.prompt([
{
type: 'input',
name: 'value',
suffix: chalk.bgGreen('?'),
message: 'Type hardwareId',
validate: input => isValidHardwareId(input),
}
])
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Binding ${hardwareId} to ${nodeId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/bind`,
data: {
hardwareId,
},
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Binding ${statusText}`))
}
async function cancelNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Canceling ${nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/cancel`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Canceling ${statusText}`))
}
async function resumeNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Resuming ${nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/resume`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Resuming ${statusText}`))
}
async function configurationsNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const fileNameInput = await inquirer.prompt([{
type: 'file-tree-selection',
name: 'nodeFile',
message: 'Choose a node file...',
}])
const formData = new FormData()
formData.append('file', fs.createReadStream(fileNameInput.nodeFile))
const axiosConfig = {
text: `Uploading ${nodeId} configuration to ${projectId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations`,
headers: formData.getHeaders(),
data: formData
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Resuming ${statusText}`))
}
async function siteinstallNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const saveFileName = path.join(process.cwd(), `Site_Install_${nodeId}.xml`)
const axiosConfig = {
text: `Downloading site install file ${nodeId}...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations/siteinstall`,
}
const { data } = await this.httpClient.request(axiosConfig)
await fsPromises.writeFile(saveFileName, data)
console.log(chalk.bgGreen(`Download site install file to ${saveFileName}`))
}
module.exports = {
getNode,
getNodeStatus,
getNodeProperties,
bindNode,
cancelNode,
resumeNode,
configurationsNode,
siteinstallNode
const fs = require('fs')
const fsPromises = require('fs').promises
const path = require('path')
const FormData = require('form-data')
const chalk = require('chalk')
const inquirer = require('inquirer')
const { logNodeStatus, logNodeProperties } = require('../../../util/logNode')
const { isValidHardwareId } = require('../../../util/validation')
const nodeCommands = [
'status', 'properties', 'delete',
'bind', 'cancel', 'resume',
'configurations', 'siteinstall', 'back',
'exit'
]
const nodeCommandsHelp = [
'[status] - Retrieving Auto Provisioning node status returns the node status information for each task that has been executed for the specified node.',
'[properties] - Retrieving Auto Provisioning node properties returns the node properties for each task that has been executed for the specified node.',
'[delete] - Delete an Auto Provisioning node removes the Auto Provisioning data for a Network Element. If a node is the last node in a project and there are no profiles associated with the project the project will automatically be deleted.',
'[bind] - Binding a hardware serial number to a node configuration associates the specified node configurations with a hardware serial number for Zero Touch Integration or Hardware Replace.',
'[cancel] - Cancelling the auto provisioning activity rolls back an AutoProvisoning workflow for Node Integration. For expansion a node is rolled back to it\'s original configuration if additional configurations have been applied to the node.',
'[resume] - Resuming the auto provisioning activity recommences an Auto Provisioning workflow that is suspended.',
'[configurations] - Uploading an auto provisioning configuration replaces a configuration file that was part of the initial Auto Provisioning node configuration.',
'[siteinstall] - Download Site Installation File (SIF) that is required to be taken on site for LMT Integration or LMT Hardware Replace.',
'[back] - Return to project\'s nodes.',
'[exit] - Exit this app.',
]
async function getNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
this.commands = nodeCommands
this.help = nodeCommandsHelp.join('\n ')
this.choices = [] // this.nodes.filter(item => item !== nodeId)
this.prompt = `${projectId} (${nodeId}) `
}
async function getNodeStatus() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}`
}
const { data: { statusEntries } } = await this.httpClient.request(axiosConfig)
logNodeStatus(statusEntries)
}
async function getNodeProperties() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s properties...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}?filter=properties`
}
const { data: { attributes, attributeGroups } } = await this.httpClient.request(axiosConfig)
logNodeProperties(attributes, attributeGroups)
}
async function bindNode() {
const hardwareId = await inquirer.prompt([
{
type: 'input',
name: 'value',
suffix: chalk.bgGreen('?'),
message: 'Type hardwareId',
validate: input => isValidHardwareId(input),
}
])
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Binding ${hardwareId} to ${nodeId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/bind`,
data: {
hardwareId,
},
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Binding ${statusText}`))
}
async function cancelNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Canceling ${nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/cancel`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Canceling ${statusText}`))
}
async function resumeNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Resuming ${nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/resume`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Resuming ${statusText}`))
}
async function configurationsNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const fileNameInput = await inquirer.prompt([{
type: 'file-tree-selection',
name: 'nodeFile',
message: 'Choose a node file...',
}])
const formData = new FormData()
formData.append('file', fs.createReadStream(fileNameInput.nodeFile))
const axiosConfig = {
text: `Uploading ${nodeId} configuration to ${projectId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations`,
headers: formData.getHeaders(),
data: formData
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Resuming ${statusText}`))
}
async function siteinstallNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
const saveFileName = path.join(process.cwd(), `Site_Install_${nodeId}.xml`)
const axiosConfig = {
text: `Downloading site install file ${nodeId}...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations/siteinstall`,
}
const { data } = await this.httpClient.request(axiosConfig)
await fsPromises.writeFile(saveFileName, data)
console.log(chalk.bgGreen(`Download site install file to ${saveFileName}`))
}
module.exports = {
getNode,
getNodeStatus,
getNodeProperties,
bindNode,
cancelNode,
resumeNode,
configurationsNode,
siteinstallNode
}

View File

@@ -1,122 +1,122 @@
const inquirer = require('inquirer')
const chalk = require('chalk')
const inquirerFileTreeSelection = require('inquirer-file-tree-selection-prompt')
const fs = require('fs')
const FormData = require('form-data')
const logProject = require('../../../util/logProject')
inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection)
const projectsCommands = ['new', 'exit']
const projectsCommandsHelp = [
'[new] - Import an Auto Provisioning project to start Auto Provisioning workflows based on the content of the AutoProvisioning project. The Auto Provisioning project file contains project related data in the projectInfo.xml file and node folders which contain configurations required to execute AutoProvisioning use-cases.',
'[exit] - Exit this app.',
'Or choose a project from list...',
]
const projectCommands = ['delete', 'back', 'exit']
const projectCommandsHelp = [
'[delete] - Delete of an Auto Provisioning project removes an Auto Provisioning project and all the Auto Provisioning data for nodes within that project. This includes removal or rollback of any ongoing Auto Provisioning workflows within that project.',
'[back] - Return to projects.',
'[exit] - Exit this app.',
'Or choose a node from list...',
]
async function getProjects() {
const axiosConfig = {
text: 'Getting projects...',
method: 'get',
url: `${this.appUrl}/projects`
}
const response = await this.httpClient.request(axiosConfig)
this.projects = response.data
this.choices = projectsChoices(this.projects)
this.commands = projectsCommands
this.help = projectsCommandsHelp.join('\n ')
this.nodes = null
return `${this.projects.length} projects`
}
async function getProjectData() {
const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Getting ${projectId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}`
}
const { data: { nodeSummary } } = await this.httpClient.request(axiosConfig)
axiosConfig.text = `Getting ${projectId}'s properties...`
axiosConfig.url += '?filter=properties'
const { data } = await this.httpClient.request(axiosConfig)
logProject(data, nodeSummary)
this.nodes = nodeSummary.map(node => node.id)
this.choices = this.nodes
this.commands = projectCommands
this.help = projectCommandsHelp.join('\n ')
this.nodeIndex = -1
return projectId
}
async function newProject() {
const fileNameInput = await inquirer.prompt([{
type: 'file-tree-selection',
name: 'projectFile',
message: 'Choose a project file...',
}])
const formData = new FormData()
formData.append('file', fs.createReadStream(fileNameInput.projectFile))
const axiosConfig = {
text: `Creating project...`,
method: 'post',
url: `${this.appUrl}/projects`,
headers: formData.getHeaders(),
data: formData
}
const { data: { id } } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Project ${id} created!`))
await this.getProjectData(id)
}
async function deleteProject() {
const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Deleting project ${projectId}...`,
method: 'delete',
url: `${this.appUrl}/projects/${projectId}`
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Delete ${statusText}`))
this.choices = projectsChoices(this.projects)
}
function projectsChoices(projects) {
return projects.map(project => {
const {
projectId,
numberOfNodes,
integrationPhaseSummary: {
cancelled,
failed,
inProgress,
successful,
suspended,
},
} = project
const statusList = [
successful && successful + '✅',
inProgress && inProgress + '⌛',
suspended && suspended + '⌚',
cancelled && cancelled + '❌',
failed && failed + '⛔',
]
const space = ' '.repeat(Math.max(14, projectId.length + 2) - projectId.length)
return `${chalk.bold(projectId)}${space}(${numberOfNodes}) ${statusList.filter(item => item).join(' ')}`
})
}
module.exports = { getProjects, getProjectData, deleteProject, newProject }
const inquirer = require('inquirer')
const chalk = require('chalk')
const inquirerFileTreeSelection = require('inquirer-file-tree-selection-prompt')
const fs = require('fs')
const FormData = require('form-data')
const logProject = require('../../../util/logProject')
inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection)
const projectsCommands = ['new', 'exit']
const projectsCommandsHelp = [
'[new] - Import an Auto Provisioning project to start Auto Provisioning workflows based on the content of the AutoProvisioning project. The Auto Provisioning project file contains project related data in the projectInfo.xml file and node folders which contain configurations required to execute AutoProvisioning use-cases.',
'[exit] - Exit this app.',
'Or choose a project from list...',
]
const projectCommands = ['delete', 'back', 'exit']
const projectCommandsHelp = [
'[delete] - Delete of an Auto Provisioning project removes an Auto Provisioning project and all the Auto Provisioning data for nodes within that project. This includes removal or rollback of any ongoing Auto Provisioning workflows within that project.',
'[back] - Return to projects.',
'[exit] - Exit this app.',
'Or choose a node from list...',
]
async function getProjects() {
const axiosConfig = {
text: 'Getting projects...',
method: 'get',
url: `${this.appUrl}/projects`
}
const response = await this.httpClient.request(axiosConfig)
this.projects = response.data
this.choices = projectsChoices(this.projects)
this.commands = projectsCommands
this.help = projectsCommandsHelp.join('\n ')
this.nodes = null
this.prompt = `${this.projects.length} projects`
}
async function getProjectData() {
const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Getting ${projectId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}`
}
const { data: { nodeSummary } } = await this.httpClient.request(axiosConfig)
axiosConfig.text = `Getting ${projectId}'s properties...`
axiosConfig.url += '?filter=properties'
const { data } = await this.httpClient.request(axiosConfig)
logProject(data, nodeSummary)
this.nodes = nodeSummary.map(node => node.id)
this.choices = this.nodes
this.commands = projectCommands
this.help = projectCommandsHelp.join('\n ')
this.nodeIndex = -1
this.prompt = projectId
}
async function newProject() {
const fileNameInput = await inquirer.prompt([{
type: 'file-tree-selection',
name: 'projectFile',
message: 'Choose a project file...',
}])
const formData = new FormData()
formData.append('file', fs.createReadStream(fileNameInput.projectFile))
const axiosConfig = {
text: `Creating project...`,
method: 'post',
url: `${this.appUrl}/projects`,
headers: formData.getHeaders(),
data: formData
}
const { data: { id } } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Project ${id} created!`))
await this.getProjectData(id)
}
async function deleteProject() {
const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Deleting project ${projectId}...`,
method: 'delete',
url: `${this.appUrl}/projects/${projectId}`
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Delete ${statusText}`))
this.choices = projectsChoices(this.projects)
}
function projectsChoices(projects) {
return projects.map(project => {
const {
projectId,
numberOfNodes,
integrationPhaseSummary: {
cancelled,
failed,
inProgress,
successful,
suspended,
},
} = project
const statusList = [
successful && successful + '✅',
inProgress && inProgress + '⌛',
suspended && suspended + '⌚',
cancelled && cancelled + '❌',
failed && failed + '⛔',
]
const space = ' '.repeat(Math.max(14, projectId.length + 2) - projectId.length)
return `${chalk.bold(projectId)}${space}(${numberOfNodes}) ${statusList.filter(item => item).join(' ')}`
})
}
module.exports = { getProjects, getProjectData, deleteProject, newProject }

View File

@@ -0,0 +1,154 @@
const logAttributes = require('../../../util/logAttributes')
const inputHandler = require('./inputHandler')
const alarms = require('./commands/alarms')
const sync = require('./commands/sync')
const initialPrompt = require('./commands/initialPrompt')
const nextObjects = require('./commands/nextObjects')
const nextAttributes = require('./commands/nextAttributes')
const setIdByCommand = require('./commands/setIdByCommand')
const show = require('./commands/show')
const up = require('./commands/up')
const config = require('./commands/config')
const end = require('./commands/end')
const setAttribute = require('./commands/setAttribute')
const description = require('./commands/description')
const get = require('./commands/get')
const set = require('./commands/set')
const commit = require('./commands/commit')
const home = require('./commands/home')
const search = require('./commands/search')
const goToFdn = require('./commands/goToFdn')
const createNext = require('../../../util/createNext')
const { mainHelp, configHelp } = require('./help')
const ENM = require('../../components/ENM')
const chalk = require('chalk')
class TopologyBrowser extends ENM {
constructor(username, password, url) {
super(username, password, url)
this.objectUrl = '/persistentObject/'
this.alarmUrl = '/alarmcontroldisplayservice/alarmMonitoring/alarmoperations/'
this.nodeTypesUrl = '/modelInfo/model/nodeTypes/'
this.nodeSearchUrl = '/managedObjects/search/v2'
this.nodePoIdsUrl = '/managedObjects/getPosByPoIds'
this.currentPoId = 0
this.nextPoId = 1
this.fdn = ''
this.childrens = []
this.poIds = []
this.isConfig = false
this.attributes = []
this.nextVariants = null
this.attributesData = []
this.attribute = null
this.networkDetails = null
this.configSet = []
this.includeNonPersistent = false
this.configCommands = ['commit', 'check', 'end', 'exit']
this.help = mainHelp
}
async initialPrompt() {
await initialPrompt.call(this)
}
async next(input) {
await this.nextVariants(input)
return createNext.call(this, input ? input : '')
}
async nextObjects(input) {
return await nextObjects.call(this, input)
}
async nextAttributes(input) {
return await nextAttributes.call(this, input)
}
setIdByCommand(command) {
return setIdByCommand.call(this, command)
}
async show(filter) {
await show.call(this, filter)
}
up() {
return up.call(this)
}
async config(fdn) {
this.help = configHelp
return await config.call(this, fdn)
}
end() {
this.help = mainHelp
end.call(this)
}
setAttribute(attribute) {
return setAttribute.call(this, attribute)
}
description() {
description.call(this)
}
get(fdn) {
get.call(this, fdn)
}
async set() {
await set.call(this)
}
async commit(fdn) {
return await commit.call(this, fdn)
}
check() {
logAttributes(this.fdn.replace(/\((\w+)\)/g, ''), this.configSet)
}
home() {
home.call(this)
}
async search() {
await search.call(this)
}
async goToFdn(fromFdn, targetFdn) {
return await goToFdn.call(this, fromFdn, targetFdn)
}
persistent() {
this.includeNonPersistent = !this.includeNonPersistent
console.log(chalk.yellow(`Include Non Persistent Atrributes Set to: ${this.includeNonPersistent}`))
}
async alarms(fdn) {
await alarms.call(this, fdn)
}
async sync(fdn) {
await sync.call(this, fdn)
}
async inputHandler() {
await inputHandler.call(this)
}
getPrompt() {
if (this.fdn.length >= 67) {
return `...${this.fdn.slice(-65)}`
}
return this.fdn
}
}
module.exports = TopologyBrowser

View File

@@ -1,127 +1,127 @@
const colors = require('colors')
const inquirer = require('inquirer')
const requestWrapper = require('../../util/requestWrapper')
const logAlarm = require('../../util/logAlarm')
const eventTimeToString = require('../../util/eventTimeToString')
const closeAlarms = { name: 'Close Alarms'.yellow, value: -1}
function logTotal(total) {
if (total === 0) {
console.log(`Total Alarms: ${total}`.green)
return
}
console.log(`Total Alarms: ${total}`.yellow)
}
function parsePoIdResponse(response) {
let total
let eventPoIds
response.data.forEach(item => {
if (item.eventPoIds) {
eventPoIds = item.eventPoIds
return
}
if (typeof item.total === 'number') {
total = item.total
return
}
})
return [total, eventPoIds]
}
async function getPoIds(url, nodes) {
const axiosConfig = {
method: 'post',
url,
data: {
filters: '',
category: 'All',
nodes,
recordLimit: 5000,
tableSettings: 'fdn#true#false,alarmingObject#true#false,presentSeverity#true#false,eventTime#true#false,insertTime#true#false,specificProblem#true#false',
timestamp: + new Date(),
sortCriteria: [
{
attribute: 'insertTime',
mode: 'desc'
}
],
advFilters: ''
}
}
const response = await requestWrapper(axiosConfig, 'Getting Alarms...')
if (!Array.isArray(response.data)) return
const [total, eventPoIds] = parsePoIdResponse(response)
logTotal(total)
if (eventPoIds) return eventPoIds.toString()
}
async function getFields(url, eventPoIds) {
const axiosConfig = {
method: 'post',
url,
data: {
eventPoIds,
tableSettings: '',
timestamp: + new Date(),
filters: '',
category: 'All'
}
}
const response = await requestWrapper(axiosConfig, 'Getting Alarms Data...')
return response.data
}
async function alarmChoices(alarmList, input) {
const filter = input ? input : ''
return alarmList
.map(al => {
const { eventPoIdAsLong, alarmingObject, presentSeverity, eventTime, specificProblem } = al
return {
name: `${presentSeverity}\t${eventTimeToString(eventTime)}\t${alarmingObject}\t${specificProblem}`,
value: eventPoIdAsLong
}
})
.filter(al => al.name.toLowerCase().includes(filter.toLowerCase()))
.concat(closeAlarms)
}
async function alarmsLoop(alarmList) {
while (true) {
const input = await inquirer.prompt([
{
type: 'autocomplete',
name: 'alarm',
message: 'Select Alarm:',
pageSize: 10,
source: async (answers, input) => await alarmChoices(alarmList, input)
}
])
if (input.alarm === closeAlarms.value) break
logAlarm(alarmList, input.alarm)
}
}
async function alarms(fdn) {
const meContextFind = fdn.match(/(NetworkElement|MeContext)=([\w-]+),?/)
if (!meContextFind) {
console.log('No alarming object in FDN!'.yellow)
return
}
const eventPoIds = await getPoIds(`${this.alarmUrl}eventpoids`, meContextFind[2])
if (!eventPoIds) return
const alarmList = await getFields(`${this.alarmUrl}getalarmlist/fields`, eventPoIds)
await alarmsLoop(alarmList)
}
const chalk = require('chalk')
const inquirer = require('inquirer')
const logAlarm = require('../../../../util/logAlarm')
const eventTimeToString = require('../../../../util/eventTimeToString')
const closeAlarms = { name: chalk.yellow('Close Alarms'), value: -1}
function logTotal(total) {
if (total === 0) {
console.log(chalk.green(`Total Alarms: ${total}`))
return
}
console.log(chalk.yellow(`Total Alarms: ${total}`))
}
function parsePoIdResponse(response) {
let total
let eventPoIds
response.data.forEach(item => {
if (item.eventPoIds) {
eventPoIds = item.eventPoIds
return
}
if (typeof item.total === 'number') {
total = item.total
return
}
})
return [total, eventPoIds]
}
async function getPoIds(url, nodes) {
const axiosConfig = {
text: 'Getting Alarms...',
method: 'post',
url,
data: {
filters: '',
category: 'All',
nodes,
recordLimit: 5000,
tableSettings: 'fdn#true#false,alarmingObject#true#false,presentSeverity#true#false,eventTime#true#false,insertTime#true#false,specificProblem#true#false',
timestamp: + new Date(),
sortCriteria: [
{
attribute: 'insertTime',
mode: 'desc'
}
],
advFilters: ''
}
}
const response = await this.httpClient.request(axiosConfig)
if (!Array.isArray(response.data)) return
const [total, eventPoIds] = parsePoIdResponse(response)
logTotal(total)
if (eventPoIds) return eventPoIds.toString()
}
async function getFields(url, eventPoIds) {
const axiosConfig = {
text: 'Getting Alarms Data...',
method: 'post',
url,
data: {
eventPoIds,
tableSettings: '',
timestamp: + new Date(),
filters: '',
category: 'All'
}
}
const response = await this.httpClient.request(axiosConfig)
return response.data
}
function alarmChoices(alarmList, input) {
const filter = input ? input : ''
return alarmList
.map(al => {
const { eventPoIdAsLong, alarmingObject, presentSeverity, eventTime, specificProblem } = al
return {
name: `${presentSeverity}\t${eventTimeToString(eventTime)}\t${alarmingObject}\t${specificProblem}`,
value: eventPoIdAsLong
}
})
.filter(al => al.name.toLowerCase().includes(filter.toLowerCase()))
.concat(closeAlarms)
}
async function alarmsLoop(alarmList) {
while (true) {
const input = await inquirer.prompt([
{
type: 'autocomplete',
name: 'alarm',
message: 'Select Alarm:',
pageSize: 10,
source: async (answers, input) => alarmChoices(alarmList, input)
}
])
if (input.alarm === closeAlarms.value) break
logAlarm(alarmList, input.alarm)
}
}
async function alarms() {
const meContextFind = this.fdn.match(/(NetworkElement|MeContext)=([\w-]+),?/)
if (!meContextFind) {
throw new Error('No alarming object in FDN!')
}
const eventPoIds = await getPoIds.call(this, `${this.alarmUrl}eventpoids`, meContextFind[2])
if (!eventPoIds) return
const alarmList = await getFields.call(this, `${this.alarmUrl}getalarmlist/fields`, eventPoIds)
await alarmsLoop(alarmList)
}
module.exports = alarms

View File

@@ -0,0 +1,35 @@
const logAttributes = require('../../../../util/logAttributes')
const logCommit = require('../../../../util/logCommit')
async function commit() {
if (!this.configSet.length) {
throw new Error('Configuration is empty❗')
}
this.fdn = this.fdn.replace(/\((\w+)\)/g, '')
logAttributes(this.fdn, this.configSet)
const axiosConfig = {
text: 'Commiting Config...',
method: 'put',
url: `${this.objectUrl}${this.currentPoId}`,
data: {
poId: this.currentPoId,
fdn: this.fdn,
attributes: this.configSet,
},
headers: {
'Content-Type': 'application/json'
}
}
const response = await this.httpClient.request(axiosConfig)
if (response.data) {
logCommit(response.data)
} else {
throw new Error('No data or response❗')
}
this.configSet.length = 0
this.end()
}
module.exports = commit

View File

@@ -1,39 +1,38 @@
const requestWrapper = require('../../util/requestWrapper')
const logDetails = require('../../util/logDetails')
async function config(fdn) {
this.isConfig = true
const axiosConfig = {
method: 'get',
url: `${this.objectUrl}${this.currentPoId}`,
params: {
includeNonPersistent: this.includeNonPersistent,
stringifyLong: true
}
}
const responseA = await requestWrapper(axiosConfig, 'Reading Attributes...')
if (!responseA.data.attributes) {
console.log('Can\'t read attributes'.red)
return fdn
}
const { attributes, namespace, namespaceVersion, neType, neVersion, networkDetails, type} = responseA.data
axiosConfig.url = `${this.objectUrl}model/${neType}/${neVersion}/${namespace}/${type}/${namespaceVersion}/attributes`
axiosConfig.params = {
includeNonPersistent: this.includeNonPersistent
}
const responseD = await requestWrapper(axiosConfig, 'Reading Attributes Data...')
if (!responseD.data.attributes) {
console.log('Can\'t read attributes data'.red)
return fdn
}
this.networkDetails = networkDetails
logDetails(networkDetails)
this.attributes = attributes
this.nextVariants = async (input) => await this.nextAttributes(input)
this.attributesData = responseD.data.attributes
return `${fdn}(config)`
}
const logDetails = require('../../../../util/logDetails')
async function config() {
this.isConfig = true
const axiosConfig = {
text: 'Reading Attributes...',
method: 'get',
url: `${this.objectUrl}${this.currentPoId}`,
params: {
includeNonPersistent: this.includeNonPersistent,
stringifyLong: true
}
}
const responseA = await this.httpClient.request(axiosConfig)
if (!responseA.data.attributes) {
throw new Error('Can\'t read attributes')
}
const { attributes, namespace, namespaceVersion, neType, neVersion, networkDetails, type} = responseA.data
axiosConfig.text = 'Reading Attributes Data...'
axiosConfig.url = `${this.objectUrl}model/${neType}/${neVersion}/${namespace}/${type}/${namespaceVersion}/attributes`
axiosConfig.params = {
includeNonPersistent: this.includeNonPersistent
}
const responseD = await this.httpClient.request(axiosConfig)
if (!responseD.data.attributes) {
throw new Error('Can\'t read attributes data')
}
this.networkDetails = networkDetails
logDetails(networkDetails)
this.attributes = attributes
this.nextVariants = async (input) => await this.nextAttributes(input)
this.attributesData = responseD.data.attributes
this.fdn = `${this.fdn}(config)`
}
module.exports = config

View File

@@ -0,0 +1,21 @@
const chalk = require('chalk')
const logAttributeData = require('../../../../util/logAttributeData')
function description() {
const attributeData = this.attributesData.find(item => item.key === this.attribute)
if (attributeData) {
logAttributeData(attributeData)
if (attributeData.complexRef) {
console.log(`${chalk.magenta(attributeData.type)}
${chalk.cyan(attributeData.complexRef.key)}: ${chalk.grey(attributeData.complexRef.description)}
`)
attributeData.complexRef.attributes.forEach(attr => logAttributeData(attr))
}
} else {
throw new Error('Attribute Not Found❗')
}
}
module.exports = description

View File

@@ -1,9 +1,11 @@
function end() {
this.isConfig = false
this.attribute = null
this.configSet.length = 0
this.nextVariants = async (input) => await this.nextObjects(input)
}
function end() {
this.fdn = this.fdn.replace(/\((\w+)\)/g, '')
this.isConfig = false
this.attribute = null
this.configSet.length = 0
this.configCommands = this.configCommands.filter(cmd => !['get', 'set', 'description'].includes(cmd) )
this.nextVariants = async (input) => await this.nextObjects(input)
}
module.exports = end

View File

@@ -0,0 +1,23 @@
const logAttributes = require('../../../../util/logAttributes')
const banner = require('../../../../util/banner')
const chalk = require('chalk')
function get() {
const syncStatus = this.networkDetails.find(item => item.key === 'syncStatus')
if (syncStatus && syncStatus.value === 'UNSYNCHRONIZED') {
console.log(chalk.yellow(`
${syncStatus.key}: ${syncStatus.value}`))
}
const attribute = this.attributes.find(item => item.key === this.attribute)
if (!attribute) {
throw new Error(`Attribute not Found: ${this.attribute}`)
}
const attributeData = this.attributesData.find(item => item.key === this.attribute)
logAttributes(this.fdn, [attribute])
console.log(` ${chalk.green('Type: ') + attributeData['type']} ${attributeData['defaultValue'] ? chalk.yellow('Default: ') + attributeData['defaultValue'] : ''}
`)
if (attributeData.constraints && attributeData.constraints.orderingConstraint) banner(attributeData)
}
module.exports = get

View File

@@ -1,28 +1,29 @@
const requestWrapper = require('../../util/requestWrapper')
async function fdn(fromFdn, targetFdn) {
const axiosConfig = {
method: 'get',
url: `${this.objectUrl}fdn/${targetFdn}`,
}
const response1 = await requestWrapper(axiosConfig, 'Browsing to FDN...')
if (!response1.data.fdn) return fromFdn
this.poIds.length = 0
const { fdn, poId } = response1.data
this.currentPoId = poId
this.nextPoId = poId
this.nextVariants = async (input) => await this.nextObjects(input)
axiosConfig.url = `${this.objectUrl}network/${this.currentPoId}/subTrees`
const response2 = await requestWrapper(axiosConfig, 'Building FDN path...')
if (response2.data) {
if (response2.data.treeNodes.length > 1) {
response2.data.treeNodes.slice(0, -1).forEach((node) => {
this.poIds.push(node.poId)
})
}
this.childrens = null
}
return fdn
}
module.exports = fdn
async function goToFdn(targetFdn) {
if (!targetFdn) {
throw new Error('Valid FDN must be supplied❗')
}
const axiosConfig = {
text: 'Browsing to FDN...',
method: 'get',
url: `${this.objectUrl}fdn/${targetFdn}`,
}
const response1 = await this.httpClient.request(axiosConfig)
this.poIds.length = 0
const { fdn, poId } = response1.data
this.currentPoId = poId
this.nextPoId = poId
this.nextVariants = async (input) => await this.nextObjects(input)
axiosConfig.text = 'Building FDN path...'
axiosConfig.url = `${this.objectUrl}network/${this.currentPoId}/subTrees`
const response2 = await this.httpClient.request(axiosConfig)
if (response2.data) {
if (response2.data.treeNodes.length > 1) {
response2.data.treeNodes.slice(0, -1).forEach((node) => {
this.poIds.push(node.poId)
})
}
this.childrens = []
}
this.fdn = fdn
}
module.exports = goToFdn

View File

@@ -1,8 +1,9 @@
function home() {
this.nextPoId = this.poIds.shift()
this.poIds.length = 0
this.nextVariants = async (input) => await this.nextObjects(input)
}
module.exports = home
function home() {
this.fdn = this.fdn.split(',', 1)[0]
this.nextPoId = this.poIds.shift()
this.poIds.length = 0
this.nextVariants = async (input) => await this.nextObjects(input)
}
module.exports = home

View File

@@ -1,23 +1,22 @@
const colors = require('colors')
const requestWrapper = require('../../util/requestWrapper')
async function initialPrompt() {
const axiosConfig = {
method: 'get',
url: `${this.objectUrl}network/-1?relativeDepth=0:-2&childDepth=1`
}
const response = await requestWrapper(axiosConfig, 'Starting Topology Browser...')
if (!response.data.treeNodes) {
console.log('Nothing in initial promt!'.red)
return
}
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}`
}
const chalk = require("chalk")
async function initialPrompt() {
const axiosConfig = {
text: 'Starting Topology Browser...',
method: 'get',
url: `${this.objectUrl}network/-1?relativeDepth=0:-2&childDepth=1`
}
const response = await this.httpClient.request(axiosConfig)
if (!response.data.treeNodes) {
throw new Error('Nothing in initial promt‼')
}
const { moType, moName, poId } = response.data.treeNodes[0]
this.currentPoId = poId
this.nextPoId = poId
this.nextVariants = async (input) => await this.nextObjects(input)
this.fdn = `${moType}=${moName}`
}
module.exports = initialPrompt

View File

@@ -0,0 +1,14 @@
async function nextAttributes(input) {
const filter = input ? input : ''
this.commands = this.configCommands
.filter(item => item.toLowerCase().includes(filter.toLowerCase()))
this.choices = this.attributes
.map(item => item.key)
.filter(item => item.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => a > b ? 1 : -1)
}
module.exports = nextAttributes

View File

@@ -0,0 +1,57 @@
const chalk = require("chalk")
const logError = require('../../../../util/logError')
const otherCommands = ['show', 'config', 'up', 'home', 'fdn', 'search', 'persistent', 'alarms', 'sync', 'exit']
function getSyncStatus(child) {
if (child.syncStatus === 'SYNCHRONIZED') return '\t\t✅'
if (child.syncStatus === 'UNSYNCHRONIZED') return '\t\t⏳'
return child.syncStatus ? '\t\t❓' : ''
}
function getRadioAccessTechnology(child) {
return child.radioAccessTechnology ? ' ' + chalk.bold.cyan(child.radioAccessTechnology.join(' ')) : ''
}
async function networkRequest() {
try {
const axiosConfig = {
text: 'Loading network data...',
method: 'get',
url: `${this.objectUrl}network/${this.currentPoId}`
}
const { data: { treeNodes } } = await this.httpClient.request(axiosConfig)
if (treeNodes) {
this.childrens = treeNodes[0].childrens
}
} catch (error) {
logError(error)
}
}
async function nextObjects(input){
const filter = input ? input : ''
if (this.currentPoId !== this.nextPoId || this.childrens.length === 0) {
this.currentPoId = this.nextPoId
this.poIds.push(this.currentPoId)
await networkRequest.call(this)
}
this.commands = otherCommands.filter(cmd => cmd.toLowerCase().includes(filter.toLowerCase()))
this.choices = this.childrens
.map(child => {
const st = getSyncStatus(child)
const rt = getRadioAccessTechnology(child)
const ne = child.neType ? ' ' + chalk.dim.gray(child.neType) : ''
return `${child.moType}=${child.moName}${st}${rt}${ne}`
})
.filter(child => child.toLowerCase().includes(filter.toLowerCase()))
.concat(filter.startsWith('show') ? [filter] : [])
.concat(filter.startsWith('fdn') ? [filter] : [])
}
module.exports = nextObjects

View File

@@ -0,0 +1,127 @@
const chalk = require('chalk')
const inquirer = require('inquirer')
const { isValidNodeName } = require('../../../../util/validation')
async function getNodeTypes() {
const axiosConfig = {
text: 'Getting Node Types...',
method: 'get',
url: this.nodeTypesUrl,
}
const response = await this.httpClient.request(axiosConfig)
return response.data
}
async function getPoIds(query) {
const axiosConfig = {
text: 'Searching Nodes...',
method: 'get',
url: this.nodeSearchUrl,
headers: {
'X-Netex-Scoping-Panel': true,
},
params: {
query,
orderby: 'moName',
orderdirection: 'asc',
}
}
const { data: { objects } } = await this.httpClient.request(axiosConfig)
return objects.map(item => item.id)
}
async function getNodesData(poList) {
const axiosConfig = {
text: 'Getting Nodes Data...',
method: 'post',
url: this.nodePoIdsUrl,
data: {
poList,
defaultMappings: ["syncStatus","managementState","radioAccessTechnology","parentNeType"],
attributeMappings:[{"moType":"MeContext","attributeNames":["neType"]}]
}
}
const response = await this.httpClient.request(axiosConfig)
return response.data
}
function resultOutput(result) {
console.log('')
result.forEach(item => {
const {
fdn,
attributes,
cmSyncStatus,
radioAccessTechnology,
} = item
console.log(`${cmSyncStatus === 'SYNCHRONIZED' ? '✅' : '⏳'} ${fdn} ${chalk.dim.grey(attributes.neType)} ${chalk.bold.cyan(radioAccessTechnology.join(' '))}`)
})
console.log('')
}
function filterTypes(input, nodeTypes){
const filter = input ? input : ''
return nodeTypes.filter(
item => item
.toLowerCase()
.includes(filter.toLowerCase())
)
}
function buildQuery(nodeName, nodeType, rat) {
const ratFilter = rat.length !== 0
? ` filter by radioAccessTechnology ${rat.length === 1 ? 'containing ' : 'contains any of '}${rat.join(',')}`
: ''
const typeFilter = nodeType === 'ALL'
? `with name = ${nodeName}`
: `of type ${nodeType} where name = ${nodeName}`
return `select all nodes ${nodeName && typeFilter}${ratFilter}`
}
async function search() {
const nodeTypes = await getNodeTypes.call(this)
nodeTypes.push('ALL')
const { nodeName, nodeType, rat } = await inquirer.prompt([
{
type: 'autocomplete',
name: 'nodeType',
message: 'Select Node Type (default ALL):',
pageSize: 10,
default: 'ALL',
source: async (answers, input) => filterTypes(input, nodeTypes)
},
{
type: 'checkbox',
name: 'rat',
message: 'Select Radio Access Technologies (default all):',
choices: [
{name: '2G'},
{name: '3G'},
{name: '4G'},
{name: '5G'},
]
},
{
type: 'input',
name: 'nodeName',
message: 'Type a Node Name:',
validate: isValidNodeName
}
])
const poList = await getPoIds.call(this, buildQuery(nodeName, nodeType, rat))
if (poList.length === 0) {
throw new Error('Nodes not found❗')
}
const result = await getNodesData.call(this, poList)
resultOutput(result)
}
module.exports = search

View File

@@ -1,28 +1,26 @@
const colors = require('colors')
const inputByType = require('../inputValue')
async function 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,
}
)
}
}
}
module.exports = set
const inputByType = require('../inputValue')
async function set() {
const attributeData = this.attributesData.find(item => item.key === this.attribute)
if (!attributeData) return
if (attributeData.writeBehavior === 'NOT_ALLOWED' || attributeData.immutable) {
throw new Error('Attribute Is ReadOnly')
}
if (this.isConfig) {
const found = this.configSet.find(item => item.key === this.attribute)
const { value } = await inputByType(attributeData)
if (found) {
found.value = value
} else {
this.configSet.push(
{
key: this.attribute,
value,
datatype: attributeData.type,
}
)
}
}
}
module.exports = set

View File

@@ -0,0 +1,12 @@
function setAttribute(attribute) {
if (!this.attributes) return false
if (!this.attributes.find(item => item.key === attribute)) return false
if (!this.attribute) {
['get', 'set', 'description'].forEach(cmd => this.configCommands.push(cmd))
}
this.attribute = attribute
return true
}
module.exports = setAttribute

View File

@@ -1,11 +1,11 @@
function setIdByCommand(command) {
const nextChild = this.childrens.filter(child => `${child.moType}=${child.moName}` === command)[0]
if (nextChild) {
this.nextPoId = nextChild.poId
return true
}
return false
}
function setIdByCommand(command) {
const nextChild = this.childrens.find(child => `${child.moType}=${child.moName}` === command)
if (nextChild) {
this.nextPoId = nextChild.poId
return true
}
return false
}
module.exports = setIdByCommand

View File

@@ -1,20 +1,20 @@
const requestWrapper = require('../../util/requestWrapper')
const logAttributes = require('../../util/logAttributes')
async function show(filter) {
const axiosConfig = {
method: 'get',
url: `${this.objectUrl}${this.currentPoId}`,
params: {
includeNonPersistent: this.includeNonPersistent,
stringifyLong: true
}
}
const response = await requestWrapper(axiosConfig)
if (!response.data.fdn || !response.data.attributes) return
logAttributes(response.data.fdn, response.data.attributes.filter(item => item.key.match(filter)))
}
const logAttributes = require('../../../../util/logAttributes')
async function show(filter) {
const axiosConfig = {
text: 'Getting Atrributes...',
method: 'get',
url: `${this.objectUrl}${this.currentPoId}`,
params: {
includeNonPersistent: this.includeNonPersistent,
stringifyLong: true
}
}
const response = await this.httpClient.request(axiosConfig)
if (!response.data.fdn || !response.data.attributes) return
logAttributes(response.data.fdn, response.data.attributes.filter(item => item.key.match(filter)))
}
module.exports = show

View File

@@ -0,0 +1,26 @@
const chalk = require("chalk")
async function sync() {
const meContextFind = this.fdn.match(/(NetworkElement|MeContext)=([\w-]+),?/)
if (!meContextFind) {
throw new Error('No sync object in FDN!')
}
const actionUrl = `${this.objectUrl}v1/perform-mo-action/NetworkElement=${meContextFind[2]},CmFunction=1?actionName=sync`
const axiosConfig = {
text: 'Initiate Node Sync...',
method: 'post',
url: actionUrl,
headers: {
'Content-Type': 'application/json'
},
}
const response = await this.httpClient.request(axiosConfig)
console.log(`
${chalk.bold(response.data.body)}
${chalk.green(response.data.title)}
`)
}
module.exports = sync

View File

@@ -1,12 +1,13 @@
function 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
}
module.exports = up
function up() {
if (this.poIds.length > 1) {
this.poIds.pop()
this.nextPoId = this.poIds.pop()
this.currentPoId = this.poIds[this.poIds.length - 1]
this.fdn = this.fdn.split(',').slice(0, -1).join(',')
return
}
throw new Error('There\'s no way up!')
}
module.exports = up

View File

@@ -0,0 +1,25 @@
const mainHelp = [
'',
'show [<valid regex>] - shows current object\'s attributes filtered with regex',
'config - enters config mode',
'up - navigate up one level',
'fdn [<valid FDN>] - navigate to FDN',
'home - navigate to root folder',
'alarms - show alarms',
'sync - initiate node CM synchronization',
'persistent - toggle persistent attributes inclusion',
'exit - logout and exit application',
].join('\n')
const configHelp = [
'',
'set - set attribute\'s value',
'get - get attribute\'s value',
'commit - commiting changes to the network',
'check - view configuration changes',
'end - exit config mode without commiting',
'exit - logout and exit application',
].join('\n')
module.exports = { mainHelp, configHelp }

View File

@@ -0,0 +1,109 @@
const inquirer = require('inquirer')
const chalk = require('chalk')
const logError = require('../../../util/logError')
const { isEmpty } = require('../../../util/validation')
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
function commandOther(tplg, command) {
const spl = command.split(/\s+/).find(item => item)
if (tplg.setIdByCommand(spl)) {
tplg.fdn = `${tplg.fdn},${spl}`
} else if (tplg.setAttribute(command)) {
tplg.fdn = tplg.fdn.replace(/\((\w+)\)/g, `(${command})`)
} else {
throw new Error('Command Unrecognized❗')
}
}
async function handleCommand(tplg, command) {
const [cmd, param] = command.split(/\s+/)
const cmdMatch = cmd.match(/\[(\w+)\]/)
const cmdName = cmdMatch ? cmdMatch[1] : cmd
switch (cmdName) {
case 'exit':
tplg.fdn = ''
break
case 'show':
await tplg.show(param ? param.trim() : '')
break
case 'config':
await tplg.config()
break
case 'set':
await tplg.set()
break
case 'commit':
await tplg.commit()
break
case 'up':
tplg.up()
break
case 'get':
tplg.get()
break
case 'check':
tplg.check()
break
case 'end':
tplg.end()
break
case 'home':
tplg.home()
break
case 'search':
await tplg.search()
break
case 'description':
tplg.description()
break
case 'persistent':
tplg.persistent()
break
case 'fdn':
await tplg.goToFdn(param ? param.trim() : '')
break
case 'alarms':
await tplg.alarms()
break
case 'sync':
await tplg.sync()
break
default:
commandOther(tplg, command)
}
}
async function inputHandler() {
await this.initialPrompt()
while (true) {
try {
const input = await inquirer.prompt([
{
type: 'autocomplete',
name: 'command',
message: chalk.blue(this.getPrompt()),
pageSize: 10,
prefix: '',
suffix: this.isConfig ? chalk.blue('#') : chalk.blue('>'),
validate: isEmpty,
source: async (answers, input) => await this.next(input),
emptyText: this.help,
}
])
await handleCommand(this, input.command)
if (!this.fdn) break
} catch (error) {
logError(error)
}
}
}
module.exports = inputHandler

View File

@@ -1,156 +1,157 @@
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
const chalk = require('chalk')
const inquirer = require('inquirer')
const banner = require('../../../util/banner')
const { isValidNumber, isValidString, checkValueRangeConstraints } = require('../../../util/validation')
async function inputInteger(attributeData) {
const message = `${chalk.yellow(attributeData.key)} { ${attributeData.unit ? attributeData.unit : chalk.gray('parrots')} } (${attributeData.type}): `
const input = await inquirer.prompt([
{
type: 'input',
name: 'value',
suffix: chalk.green('?'),
message,
default: attributeData.defaultValue,
validate: input => isValidNumber(input, attributeData.constraints),
}
])
return +input.value
}
async function inputEnumRef(attributeData) {
const message = `Select Value For ${chalk.yellow(attributeData.key)}: `
const input = await inquirer.prompt([
{
type: 'list',
name: 'value',
suffix: chalk.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 ${chalk.yellow(attributeData.key)}:`
const input = await inquirer.prompt([
{
type: 'list',
name: 'value',
suffix: chalk.green('?'),
message,
choices: variants,
default: String(attributeData.defaultValue),
}
])
return {
true: true,
false: false,
null: null
}[input.value]
}
async function inputString(attributeData) {
const message = `${chalk.yellow(attributeData.key)} (${attributeData.type}): `
const input = await inquirer.prompt([
{
type: 'input',
name: 'value',
suffix: chalk.green('?'),
message,
default: attributeData.defaultValue,
validate: input => isValidString(input, attributeData.constraints),
}
])
return input.value
}
async function inputList(attributeData) {
const message = `${chalk.yellow(attributeData.key)} List Of (${attributeData.listReference.type}) Size: `
const result = []
const { value } = await inquirer.prompt([
{
type: 'number',
name: 'value',
suffix: chalk.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(chalk.red('>>Array Values Should Be Unique'))
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,
LONG: 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

View File

@@ -1,48 +1,52 @@
const axios = require('axios')
const chalk = require('chalk')
const https = require('https')
const axiosCookieJarSupport = require('axios-cookiejar-support').default
const tough = require('tough-cookie')
const SpinnerWithCounter = require('../../../util/SpinnerWithCounter')
axiosCookieJarSupport(axios)
let spinner = new SpinnerWithCounter()
function beforeRequest(config) {
const {text = 'Executing request...', ...newConfig} = config
spinner.start(text)
return newConfig
}
function errorRequest(error) {
spinner.fail()
return Promise.reject(error)
}
function beforeResponse(response) {
spinner.succeed()
return response
}
function axiosHttpClient(url) {
const axiosClient = axios.create({
baseURL: url,
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
withCredentials: true,
jar: new tough.CookieJar(),
})
axiosClient.interceptors.request.use(beforeRequest, errorRequest)
axiosClient.interceptors.response.use(beforeResponse, errorRequest)
return axiosClient
}
const axios = require('axios')
const https = require('https')
const axiosCookieJarSupport = require('axios-cookiejar-support').default
const tough = require('tough-cookie')
const logError = require('../../util/logError')
const SpinnerWithCounter = require('./SpinnerWithCounter')
axiosCookieJarSupport(axios)
let spinner = new SpinnerWithCounter()
function beforeRequest(config) {
const {text = 'Executing request...', ...newConfig} = config
spinner.start(text)
return newConfig
}
function errorRequest(error) {
spinner.fail()
if (401 === error.response.status) {
logError(error)
process.exit(1)
}
return Promise.reject(error)
}
function beforeResponse(response) {
spinner.succeed()
return response
}
function axiosHttpClient(url) {
const axiosClient = axios.create({
baseURL: url,
httpsAgent: new https.Agent({
rejectUnauthorized: false
}),
withCredentials: true,
jar: new tough.CookieJar(),
})
axiosClient.interceptors.request.use(beforeRequest, errorRequest)
axiosClient.interceptors.response.use(beforeResponse, errorRequest)
return axiosClient
}
module.exports = axiosHttpClient

63
lib/components/ENM/ENM.js → lib/components/ENM.js Normal file → Executable file
View File

@@ -1,32 +1,33 @@
const chalk = require('chalk')
const axiosHttpClient = require('../AxiosHttpClient/AxiosHttpClient')
class ENM {
constructor(username, password, url) {
this.logoutUrl = '/logout'
this.loginUrl = `/login?IDToken1=${username}&IDToken2=${password}`
this.httpClient = axiosHttpClient(url)
}
async login() {
const axiosConfig = {
text: 'Login in...',
method: 'post',
url: this.loginUrl
}
const response = await this.httpClient.request(axiosConfig)
return response.data
}
async logout() {
const axiosConfig = {
text: 'Logout...',
method: 'get',
url: this.logoutUrl
}
await this.httpClient.request(axiosConfig)
}
}
const axiosHttpClient = require('./AxiosHttpClient')
class ENM {
constructor(username, password, url) {
this.logoutUrl = '/logout'
this.loginUrl = `/login?IDToken1=${username}&IDToken2=${password}`
this.commands = null
this.choices = null
this.httpClient = axiosHttpClient(url)
}
async login() {
const axiosConfig = {
text: 'Login in...',
method: 'post',
url: this.loginUrl
}
const response = await this.httpClient.request(axiosConfig)
return response.data
}
async logout() {
const axiosConfig = {
text: 'Logout...',
method: 'get',
url: this.logoutUrl
}
await this.httpClient.request(axiosConfig)
}
}
module.exports = ENM

View File

@@ -1,35 +1,35 @@
const ora = require('ora')
class SpinnerWithCounter {
constructor() {
this.spinner = null
this.counter = 0
}
start(text) {
if (!this.spinner) {
this.spinner = ora(text)
this.spinner.start()
}
this.counter = ++this.counter
}
succeed() {
this.counter = --this.counter
if (this.spinner && this.counter === 0) {
this.spinner.succeed()
this.spinner = null
}
}
fail() {
this.counter = --this.counter
if (this.spinner && this.counter === 0) {
this.spinner.fail()
this.spinner = null
}
}
}
const ora = require('ora')
class SpinnerWithCounter {
constructor() {
this.spinner = null
this.counter = 0
}
start(text) {
if (!this.spinner) {
this.spinner = ora(text)
this.spinner.start()
}
this.counter = ++this.counter
}
succeed() {
this.counter = --this.counter
if (this.spinner && this.counter === 0) {
this.spinner.succeed()
this.spinner = null
}
}
fail() {
this.counter = --this.counter
if (this.spinner && this.counter === 0) {
this.spinner.fail()
this.spinner = null
}
}
}
module.exports = SpinnerWithCounter

View File

@@ -1,132 +0,0 @@
const logAttributes = require('../util/logAttributes')
const inputHandler = require('./inputHandler')
const alarms = require('../commands/alarms')
const sync = require('../commands/sync')
const initialPrompt = require('../commands/initialPrompt')
const nextObjects = require('../commands/nextObjects')
const nextAttributes = require('../commands/nextAttributes')
const setIdByCommand = require('../commands/setIdByCommand')
const show = require('../commands/show')
const up = require('../commands/up')
const config = require('../commands/config')
const end = require('../commands/end')
const setAttribute = require('../commands/setAttribute')
const description = require('../commands/description')
const get = require('../commands/get')
const set = require('../commands/set')
const commit = require('../commands/commit')
const home = require('../commands/home')
const fdn = require('../commands/fdn')
const ENM = require('../ENM/ENM')
class TopologyBrowser extends ENM {
constructor(username, password, url) {
super(username, password, url)
this.objectUrl = '/persistentObject/'
this.alarmUrl = '/alarmcontroldisplayservice/alarmMonitoring/alarmoperations/'
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.includeNonPersistent = false
this.configCommands = ['commit', 'check', 'end', 'persistent', 'exit']
this.help = 'No results...'
}
async initialPrompt() {
return await initialPrompt.call(this)
}
async next(input) {
return await this.nextVariants(input)
}
async nextObjects(input) {
return await nextObjects.call(this, input)
}
async nextAttributes(input) {
return await nextAttributes.call(this, input)
}
setIdByCommand(command) {
return setIdByCommand.call(this, command)
}
async show(filter) {
await show.call(this, filter)
}
up() {
return up.call(this)
}
async config(fdn) {
return await config.call(this, fdn)
}
end() {
end.call(this)
}
setAttribute(attribute) {
return setAttribute.call(this, attribute)
}
description() {
description.call(this)
}
get(fdn) {
get.call(this, fdn)
}
async set() {
await set.call(this)
}
async commit(fdn) {
return await commit.call(this, fdn)
}
check(fdn) {
logAttributes(fdn, this.configSet)
}
home() {
home.call(this)
}
async fdn(fromFdn, targetFdn) {
return await fdn.call(this, fromFdn, targetFdn)
}
persistent() {
this.includeNonPersistent = !this.includeNonPersistent
console.log(`Include Non Persistent Atrributes Set to: ${this.includeNonPersistent}`.yellow)
}
async alarms(fdn) {
await alarms.call(this, fdn)
}
async sync(fdn) {
await sync.call(this, fdn)
}
async inputHandler() {
await inputHandler.call(this)
}
}
module.exports = TopologyBrowser

View File

@@ -1,34 +0,0 @@
const colors = require('colors')
const logAttributes = require('../../util/logAttributes')
const requestWrapper = require('../../util/requestWrapper')
const logCommit = require('../../util/logCommit')
async function commit(fdn) {
logAttributes(fdn, this.configSet)
this.configSet.forEach(item => delete item.from)
const axiosConfig = {
method: 'put',
url: `${this.objectUrl}${this.currentPoId}`,
data: {
poId: this.currentPoId,
fdn,
attributes: this.configSet,
},
headers: {
'Content-Type': 'application/json'
}
}
const response = await requestWrapper(axiosConfig, 'Commiting Config...')
if (response.data) {
logCommit(response.data)
} else {
console.log('No data or response!'.red)
}
this.configSet.length = 0
this.end()
return fdn
}
module.exports = commit

View File

@@ -1,21 +0,0 @@
const colors = require('colors')
const logAttributeData = require('../../util/logAttributeData')
function 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)
}
}
module.exports = description

View File

@@ -1,20 +0,0 @@
const colors = require('colors')
const logAttributes = require('../../util/logAttributes')
const banner = require('../../util/banner')
function 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)
}
module.exports = get

View File

@@ -1,14 +0,0 @@
async function nextAttributes(input) {
const filter = input ? input : ''
let result = this.attributes.map(item => item.key).sort((a, b) => a > b ? 1 : -1)
.concat(this.configCommands)
.filter(item => item.toLowerCase().includes(filter.toLowerCase()))
if (result.includes(filter)) {
result = result.filter(item => item !== filter)
result.unshift(filter)
}
return result
}
module.exports = nextAttributes

View File

@@ -1,34 +0,0 @@
const requestWrapper = require('../../util/requestWrapper')
const otherCommands = ['show', 'config', 'up', 'home', 'fdn', 'persistent', 'alarms', 'sync', 'exit']
async function nextObjects(input){
const filter = input ? input : ''
if (this.currentPoId !== this.nextPoId || !this.childrens) {
this.currentPoId = this.nextPoId
this.poIds.push(this.currentPoId)
const axiosConfig = {
method: 'get',
url: `${this.objectUrl}network/${this.currentPoId}`
}
const response = await requestWrapper(axiosConfig)
if (response.data.treeNodes) {
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] : [])
.concat(filter.startsWith('fdn') ? [filter] : [])
if (result.includes(filter)) {
result = result.filter(item => item !== filter)
result.unshift(filter)
}
return result
}
module.exports = nextObjects

View File

@@ -1,14 +0,0 @@
function setAttribute(attribute) {
if (!this.attributes) return false
if (!this.attributes.filter(item => item.key === attribute)[0]) return false
if (!this.attribute) {
this.configCommands.push('get')
this.configCommands.push('set')
this.configCommands.push('description')
}
this.attribute = attribute
return true
}
module.exports = setAttribute

View File

@@ -1,30 +0,0 @@
const colors = require('colors')
const requestWrapper = require('../../util/requestWrapper')
async function sync(fdn) {
const meContextFind = fdn.match(/(NetworkElement|MeContext)=([\w-]+),?/)
if (!meContextFind) {
console.log('No sync object in FDN!'.yellow)
return
}
const actionUrl = `${this.objectUrl}v1/perform-mo-action/NetworkElement=${meContextFind[2]},CmFunction=1?actionName=sync`
const axiosConfig = {
method: 'post',
url: actionUrl,
headers: {
'Content-Type': 'application/json'
},
}
const response = await requestWrapper(axiosConfig, 'Initiate Node Sync...')
if (response.status === 200) {
console.log(`
${response.data.body.bold}
${response.data.title.green}
`)
}
}
module.exports = sync

View File

@@ -1,136 +0,0 @@
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) {
const [cmd, param] = command.split(/\s+/)
switch (cmd) {
case 'exit':
return
case 'show':
await tplg.show(param ? param.trim() : '')
break
case 'config':
fdn = await tplg.config(fdn)
break
case 'set':
await tplg.set()
break
case 'commit':
fdn = await tplg.commit(fdn.replace(/\((\w+)\)/g, ''))
break
case 'up':
fdn = commndUp(tplg, fdn)
break
case 'get':
tplg.get(fdn)
break
case 'check':
tplg.check(fdn.replace(/\((\w+)\)/g, ''))
break
case 'end':
tplg.end()
fdn = fdn.replace(/\((\w+)\)/g, '')
break
case 'home':
tplg.home()
fdn = fdn.split(',', 1)[0]
break
case 'description':
tplg.description()
break
case 'persistent':
tplg.persistent()
break
case 'fdn':
fdn = await tplg.fdn(fdn, param ? param.trim() : '')
break
case 'alarms':
await tplg.alarms(fdn)
break
case 'sync':
await tplg.sync(fdn)
break
default:
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) => await tplg.next(input),
emptyText: prvn.help,
}
])
fdn = await handleCommand(tplg, fdn, input.command)
if (!fdn) break
({ prefix, prompt } = buildPrompt(fdn))
} catch (error) {
logError(error)
}
}
}
async function inputHandler() {
try {
await inputHandlerLoop(this)
} catch (error) {
logError(error)
}
}
module.exports = inputHandler

86
package.json Normal file → Executable file
View File

@@ -1,43 +1,43 @@
{
"name": "enm-cli",
"version": "1.0.0",
"description": "This is a cli application for Ericsson Network Manager (ENM)",
"bin": "./bin/enm-cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Ericsson",
"ENM",
"Network",
"Manager",
"LTE",
"3G",
"4G",
"5G",
"Automation",
"GSM",
"UMTS",
"OSS",
"Cellular",
"REST",
"AutoProvisioning",
"TopologyBrowser",
"API"
],
"author": "vvsviridov",
"license": "MIT",
"dependencies": {
"axios": "^0.21.2",
"axios-cookiejar-support": "^1.0.1",
"chalk": "^4.1.2",
"commander": "^9.1.0",
"dotenv": "^16.0.0",
"form-data": "^4.0.0",
"inquirer": "^8.2.2",
"inquirer-autocomplete-prompt": "^2.0.0",
"inquirer-file-tree-selection-prompt": "^1.0.18",
"ora": "^5.4.1",
"tough-cookie": "^4.0.0"
}
}
{
"name": "enm-cli",
"version": "1.0.0",
"description": "This is a cli application for Ericsson Network Manager (ENM)",
"bin": "./bin/enm-cli.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"Ericsson",
"ENM",
"Network",
"Manager",
"LTE",
"3G",
"4G",
"5G",
"Automation",
"GSM",
"UMTS",
"OSS",
"Cellular",
"REST",
"AutoProvisioning",
"TopologyBrowser",
"API"
],
"author": "vvsviridov",
"license": "MIT",
"dependencies": {
"axios": "^0.21.2",
"axios-cookiejar-support": "^1.0.1",
"chalk": "^4.1.2",
"commander": "^9.1.0",
"dotenv": "^16.0.0",
"form-data": "^4.0.0",
"inquirer": "^8.2.2",
"inquirer-autocomplete-prompt": "^2.0.0",
"inquirer-file-tree-selection-prompt": "^1.0.18",
"ora": "^5.4.1",
"tough-cookie": "^4.0.0"
}
}

11
util/banner.js Executable file
View File

@@ -0,0 +1,11 @@
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

32
util/createNext.js Normal file → Executable file
View File

@@ -1,17 +1,17 @@
const inquirer = require('inquirer')
function createNext(filter) {
const separator = new inquirer.Separator()
const commands = this.commands.filter(cmd => cmd.toLowerCase().includes(filter.toLowerCase()))
const choices = this.choices.filter(choice => choice.toLowerCase().includes(filter.toLowerCase()))
return [
separator,
...commands.map(cmd => `[${cmd}]`),
separator,
...choices,
]
}
const inquirer = require('inquirer')
function createNext(filter) {
const separator = new inquirer.Separator()
const commands = this.commands.filter(cmd => cmd.toLowerCase().includes(filter.toLowerCase()))
const choices = this.choices.filter(choice => choice.toLowerCase().includes(filter.toLowerCase()))
return [
...choices,
separator,
...commands.map(cmd => `[${cmd}]`),
separator,
]
}
module.exports = createNext

10
util/eventTimeToString.js Executable file
View File

@@ -0,0 +1,10 @@
function eventTimeToString(eventTime) {
if (!eventTime) return ''
return new Date(eventTime).toISOString().slice(0, -1).split('T').join(' ')
// return `${eventDateTime.toLocaleDateString()} ${eventDateTime.toLocaleTimeString()}`
}
module.exports = eventTimeToString

26
util/logAlarm.js Executable file
View File

@@ -0,0 +1,26 @@
const chalk = require('chalk')
const eventTimeToString = require('./eventTimeToString')
const timeValues = [
'eventTime',
'insertTime',
'ceaseTime',
'ackTime',
]
function logAlarm(alarmList, eventPoId) {
const alarm = alarmList.filter(item => item.eventPoIdAsLong === eventPoId)[0]
timeValues.forEach(value => alarm[value] = eventTimeToString(alarm[value]))
console.log(
JSON.stringify(alarm, null, 2)
.replace(/["(){}\[\]]/mg, '')
.replace(/,$/mg, '')
.replace(/^(\s{2}\w+):/mg, chalk.green('$1:'))
)
}
module.exports = logAlarm

84
util/logAttributeData.js Executable file
View File

@@ -0,0 +1,84 @@
const chalk = require('chalk')
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(`${chalk.blue(attrName.toLocaleUpperCase())}
${attribute}
`)
}
}
function logConstraints(constraints, output) {
output.push(`${chalk.blue(Object.keys({constraints}).pop().toLocaleUpperCase())}`)
if (constraints.valueRangeConstraints) {
constraints.valueRangeConstraints.forEach(item => {
output.push(` ${chalk.yellow('Range')}: ${item.minValue}..${item.maxValue}`)
})
}
['nullable', 'validContentRegex', 'valueResolution'].forEach(key => {
if (Object.keys(constraints).includes(key)) {
output.push(` ${chalk.yellow(key.replace(/([A-Z])/g, ' $1').replace(/^([a-z])/g, (l) => l.toUpperCase()))}: ${constraints[key]}`)
}
})
}
function logEnumeration(enumeration, output) {
output.push(`${chalk.blue(Object.keys({enumeration}).pop().toLocaleUpperCase())}
${chalk.cyan(enumeration.key)}
${enumeration.description}`)
enumeration.enumMembers.forEach(item => output.push(` ${chalk.yellow(item.key)} (${item.value}): -- ${chalk.gray(item.description)}`))
}
function logList(listReference, output) {
output.push(`${chalk.blue(Object.keys({listReference}).pop().toLocaleUpperCase())}
${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 = [`
${chalk.yellow.bold(attributeData['key'])}: ${chalk.green(attributeData['type'])} ${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

34
util/logAttributes.js Executable file
View File

@@ -0,0 +1,34 @@
const chalk = require('chalk')
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, chalk.green('$1:'))
.replace(/^(\s{4}\w+):/mg, chalk.yellow('$1:'))
.replace(/^(\s{5}\w+):/mg, chalk.cyan('$1:'))
}
function logAttributes(fdn, attributes) {
const output = `
${chalk.yellow.bold('FDN')}: ${chalk.bold(fdn)}
${colorize(attributes)}`
console.log(output)
}
module.exports = logAttributes

10
util/logCommit.js Executable file
View File

@@ -0,0 +1,10 @@
const chalk = require('chalk')
function logCommit(commitResult) {
if (commitResult.title === 'Success') {
console.log(chalk.green(commitResult.title))
}
}
module.exports = logCommit

11
util/logDetails.js Executable file
View File

@@ -0,0 +1,11 @@
const chalk = require('chalk')
function logDetails(networkDetails) {
const output = networkDetails.map(details => ` ${chalk.gray(details.key)}: ${details.value === 'UNSYNCHRONIZED' ? '⌛ ' + chalk.yellow(details.value): chalk.gray(details.value)}`)
console.log(`
${output.join('\n')}
`)
}
module.exports = logDetails

82
util/logError.js Normal file → Executable file
View File

@@ -1,29 +1,53 @@
const chalk = require('chalk')
function logError(err) {
if (err.response && err.response.data && err.response.data.errorTitle) {
const {
errorTitle = 'Error',
errorBody = 'No error body',
errorDetails = null
} = err.response.data
console.log(`
⚠️ ${chalk.bold.bgRed(errorTitle)}
${chalk.yellow(errorBody || '')}${errorDetails ? '\n' + errorDetails.toString() : ''}
`)
} else {
const {
name = 'Error',
message = 'No error message',
stack = null,
} = err
console.log(`
${chalk.bold.bgRed(name)}
${chalk.yellow(message)}
${chalk.dim(stack && process.env.NODE_ENV === 'development' ? stack : '')}
`)
}
}
module.exports = logError
const chalk = require('chalk')
function logError(err) {
try {
if (!err.response) {
const {
name = 'Error',
message = 'No error message',
stack = null,
} = err
console.log(`
${chalk.bold.bgRed(name)}
${chalk.yellow(message)}${chalk.dim(stack && process.env.NODE_ENV === 'development' ? '\n' + stack : '')}
`)
return
}
if (err.response.data) {
const { data } = err.response
// other http error
let errorTitle = `${err.response.status}: ${err.response.statusText}`
let errorBody = data.code
let errorDetails = data.message
if (typeof data !== 'object') {
errorBody = data
}
if (data.errorCode) {
errorBody = `${data.errorCode}: ${data.userMessage.title}`
errorDetails = data.userMessage.body
}
// prvn error
if (data.errorTitle) {
errorTitle = data.errorTitle
errorBody = data.errorBody
errorDetails = data.errorDetails
}
// tplg error
if (data.title) {
errorTitle = `${data.errorCode}: ${data.title}`
errorBody = data.body
errorDetails = data.detail
}
console.log(`
⚠️ ${chalk.bold.bgRed(errorTitle)}
${chalk.yellow(errorBody)}${errorDetails ? '\n' + errorDetails.toString() : ''}
`)
}
} catch (error) {
console.log(error)
}
}
module.exports = logError

96
util/logNode.js Normal file → Executable file
View File

@@ -1,49 +1,49 @@
const chalk = require('chalk')
function logNodeStatus(statusEntries, indent = 4) {
if (!statusEntries) {
throw new Error('No node status entries!')
}
console.log('')
statusEntries.forEach(entry => {
const { task, progress, timestamp, additionalInfo } = entry
console.log(`${' '.repeat(indent)}${chalk.cyan(task)} ${progress} at ${chalk.italic.gray(timestamp)}`)
if (additionalInfo) {
additionalInfo.trim().split('\n').forEach(info => {
console.log(`${' '.repeat(indent + 2) + chalk.dim(info)}`)
})
}
})
console.log('')
}
function logNodeProperties(attributes, attributeGroups, indent = 4) {
if (!attributeGroups) {
throw new Error('No node attribute groups!')
}
console.log('')
logAttributes(attributes)
console.log('')
attributeGroups.forEach(attributeGroup => {
const { type, properties } = attributeGroup
console.log(`${' '.repeat(indent)}${chalk.bold.yellow(type + '↓')}`)
logAttributes(properties, indent + 2)
console.log('')
})
}
function logAttributes(attributes, indent = 4) {
if (!attributes) {
throw new Error('No node attributes!')
}
attributes.forEach(attribute => {
const { name, value } = attribute
console.log(`${' '.repeat(indent)}${name ? chalk.bold.cyan(name + ': ') : ''}${value}`)
})
}
const chalk = require('chalk')
function logNodeStatus(statusEntries, indent = 4) {
if (!statusEntries) {
throw new Error('No node status entries')
}
console.log('')
statusEntries.forEach(entry => {
const { task, progress, timestamp, additionalInfo } = entry
console.log(`${' '.repeat(indent)}${chalk.cyan(task)} ${progress} at ${chalk.italic.gray(timestamp)}`)
if (additionalInfo) {
additionalInfo.trim().split('\n').forEach(info => {
console.log(`${' '.repeat(indent + 2) + chalk.dim(info)}`)
})
}
})
console.log('')
}
function logNodeProperties(attributes, attributeGroups, indent = 4) {
if (!attributeGroups) {
throw new Error('No node attribute groups')
}
console.log('')
logAttributes(attributes)
console.log('')
attributeGroups.forEach(attributeGroup => {
const { type, properties } = attributeGroup
console.log(`${' '.repeat(indent)}${chalk.bold.yellow(type + '↓')}`)
logAttributes(properties, indent + 2)
console.log('')
})
}
function logAttributes(attributes, indent = 4) {
if (!attributes) {
throw new Error('No node attributes')
}
attributes.forEach(attribute => {
const { name, value } = attribute
console.log(`${' '.repeat(indent)}${name ? chalk.bold.cyan(name + ': ') : ''}${value}`)
})
}
module.exports = { logNodeStatus, logNodeProperties }

70
util/logProject.js Normal file → Executable file
View File

@@ -1,36 +1,36 @@
const chalk = require('chalk')
function logProject(data, nodeSummary) {
if (!data || !nodeSummary) {
throw new Error('No project data or node summary!')
}
const {
id: projectId,
description,
creator,
creationDate,
nodes,
} = data
console.log(`
${chalk.italic.yellowBright('Project id')} : ${chalk.bold.inverse(projectId)}
${chalk.italic.yellowBright('Author')} : ${chalk.underline(creator)}
${chalk.italic.yellowBright('Creation Date')} : ${chalk.dim.gray(creationDate)}
${chalk.italic.yellowBright('Description')} : ${description}
${chalk.italic.yellowBright('Nodes')} :
`)
nodes.forEach(node => {
const { id: nodeId, type, identifier, ipAddress } = node
const { status, state } = nodeSummary.find(item => item.id === nodeId)
console.log(` ${chalk.cyan(nodeId)}
${type}
${identifier}
${ipAddress}
${status === 'Successful' ? chalk.greenBright(status) : chalk.redBright(status) }
${state}
`)
})
}
const chalk = require('chalk')
function logProject(data, nodeSummary) {
if (!data || !nodeSummary) {
throw new Error('No project data or node summary!')
}
const {
id: projectId,
description,
creator,
creationDate,
nodes,
} = data
console.log(`
${chalk.italic.yellowBright('Project id')} : ${chalk.bold.inverse(projectId)}
${chalk.italic.yellowBright('Author')} : ${chalk.underline(creator)}
${chalk.italic.yellowBright('Creation Date')} : ${chalk.dim.gray(creationDate)}
${chalk.italic.yellowBright('Description')} : ${description}
${chalk.italic.yellowBright('Nodes')} :
`)
nodes.forEach(node => {
const { id: nodeId, type, identifier, ipAddress } = node
const { status, state } = nodeSummary.find(item => item.id === nodeId)
console.log(` ${chalk.cyan(nodeId)}
${type}
${identifier}
${ipAddress}
${status === 'Successful' ? chalk.greenBright(status) : chalk.redBright(status) }
${state}
`)
})
}
module.exports = logProject

73
util/validation.js Normal file → Executable file
View File

@@ -1,10 +1,63 @@
const chalk = require('chalk')
const isEmpty = input => (input === '' ? chalk.bgRed('Empty Inputs not Allowed') : true)
const isValidHardwareId = input => (input.match(/[A-HJ-NP-Z0-9]{13}/) ? true : chalk.bgRed(`The Ericsson Hardware Serial Number frame consists of 13 alphanumeric characters.
Character set is the letters A-Z with the exception of O and I, and digits 0-9.`))
module.exports = { isEmpty, isValidHardwareId }
const chalk = require('chalk')
const isEmpty = input => (input === '' ? chalk.bgRed('Empty Inputs not Allowed') : true)
const isValidHardwareId = input => (input.match(/[A-HJ-NP-Z0-9]{13}/) ? true : chalk.bgRed(`The Ericsson Hardware Serial Number frame consists of 13 alphanumeric characters.
Character set is the letters A-Z with the exception of O and I, and digits 0-9.`))
const isValidNumber = (input, constraints) => {
if (constraints) {
const test = input ? input : ''
if (!constraints.nullable && test === 'null') return chalk.red('Value Can\'t Be a null')
if (constraints.nullable && test === 'null') return true
if (!test.toString().match(/-?[0-9]+/)) return chalk.red('Input Is Not a Number')
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 chalk.red('Value Can\'t Be a null')
if (constraints.nullable && test === 'null') return true
if (constraints.validContentRegex && !test.match(constraints.validContentRegex)) return chalk.red('Input Doesn\'t Match RegEx')
const checkResult = checkValueRangeConstraints(test.length, constraints)
if (checkResult) return checkResult
}
return true
}
const checkValueRangeConstraints = (value, constraints) => {
let min
let max
if (constraints.valueRangeConstraints) {
const inRange = constraints.valueRangeConstraints.some(item => {
min = item.minValue
max = item.maxValue
return min <= value && max >= value
})
errStrArr = constraints.valueRangeConstraints.map(item => `${item.minValue}..${item.maxValue}`)
if (!inRange) return chalk.red(`Input is Outside Allowed Range: ${errStrArr.join(', ')}`)
}
}
const isValidNodeName = (input) => {
if (input.match('[a-zA-Z0-9-_.\\/:$]+') || input === '') {
return true
}
return chalk.red('Node Name is Invalid')
}
module.exports = {
isEmpty,
isValidHardwareId,
isValidNumber,
isValidString,
checkValueRangeConstraints,
isValidNodeName,
}