mirror of
https://github.com/vvsviridov/enm-cli.git
synced 2025-10-23 08:22:21 +00:00
first commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
.vscode
|
||||||
|
package-lock.json
|
||||||
|
.env
|
21
LICENSE
Normal file
21
LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2021 Vyacheslav Sviridov
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
194
README.md
Normal file
194
README.md
Normal file
@@ -0,0 +1,194 @@
|
|||||||
|
# Cli application based on ENM AutoProvisioning API
|
||||||
|
|
||||||
|
[](https://github.com/vvsviridov/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
|
93
bin/enm-cli.js
Normal file
93
bin/enm-cli.js
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
#!/usr/bin/env node
|
||||||
|
|
||||||
|
const program = require('commander')
|
||||||
|
const pkg = require('../package.json')
|
||||||
|
const inquirer = require('inquirer')
|
||||||
|
|
||||||
|
require('dotenv').config()
|
||||||
|
|
||||||
|
const AutoProvisioning = require('../lib/components/AutoProvisioning/AutoProvisioning')
|
||||||
|
const TopologyBrowser = require('../lib/components/TopologyBrowser/TopologyBrowser')
|
||||||
|
|
||||||
|
const inputHandler = require('../lib/components/AutoProvisioning/inputHandler')
|
||||||
|
const logError = require('../util/logError')
|
||||||
|
|
||||||
|
program
|
||||||
|
.version(pkg.version)
|
||||||
|
.option('-l, --login <letters>', 'ENM User Login')
|
||||||
|
.option('-p, --password <letters>', 'ENM User Password')
|
||||||
|
.option('-a, --application <letters>', 'Start specified application')
|
||||||
|
.requiredOption('-u, --url <letters>', 'ENM Url')
|
||||||
|
.parse(process.argv)
|
||||||
|
|
||||||
|
|
||||||
|
const options = program.opts()
|
||||||
|
|
||||||
|
const applications = ['tplg', 'prvn']
|
||||||
|
|
||||||
|
|
||||||
|
async function promptUsername() {
|
||||||
|
if (process.env.LOGIN) return process.env.LOGIN
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'value',
|
||||||
|
suffix: chalk.bgGreen('?'),
|
||||||
|
message: 'Type ENM login',
|
||||||
|
// validate: input => isValidNumber(input, attributeData.constraints),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return input.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function promptPassword() {
|
||||||
|
if (process.env.PASSWORD) return process.env.PASSWORD
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'password',
|
||||||
|
name: 'value',
|
||||||
|
message: `Type ${options.login}'s ENM password`,
|
||||||
|
// validate: input => isValidNumber(input, attributeData.constraints),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
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())()
|
90
lib/components/AutoProvisioning/AutoProvisioning.js
Normal file
90
lib/components/AutoProvisioning/AutoProvisioning.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = AutoProvisioning
|
107
lib/components/AutoProvisioning/inputHandler.js
Normal file
107
lib/components/AutoProvisioning/inputHandler.js
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = inputHandler
|
165
lib/components/AutoProvisioning/nodes.js
Normal file
165
lib/components/AutoProvisioning/nodes.js
Normal file
@@ -0,0 +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
|
||||||
|
}
|
122
lib/components/AutoProvisioning/projects.js
Normal file
122
lib/components/AutoProvisioning/projects.js
Normal file
@@ -0,0 +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 }
|
48
lib/components/AxiosHttpClient/AxiosHttpClient.js
Normal file
48
lib/components/AxiosHttpClient/AxiosHttpClient.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = axiosHttpClient
|
32
lib/components/ENM/ENM.js
Normal file
32
lib/components/ENM/ENM.js
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = ENM
|
132
lib/components/TopologyBrowser/TopologyBrowser.js
Normal file
132
lib/components/TopologyBrowser/TopologyBrowser.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
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
|
127
lib/components/TopologyBrowser/commands/alarms.js
Normal file
127
lib/components/TopologyBrowser/commands/alarms.js
Normal file
@@ -0,0 +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)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = alarms
|
34
lib/components/TopologyBrowser/commands/commit.js
Normal file
34
lib/components/TopologyBrowser/commands/commit.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
39
lib/components/TopologyBrowser/commands/config.js
Normal file
39
lib/components/TopologyBrowser/commands/config.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
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)`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = config
|
21
lib/components/TopologyBrowser/commands/description.js
Normal file
21
lib/components/TopologyBrowser/commands/description.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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
|
9
lib/components/TopologyBrowser/commands/end.js
Normal file
9
lib/components/TopologyBrowser/commands/end.js
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
function end() {
|
||||||
|
this.isConfig = false
|
||||||
|
this.attribute = null
|
||||||
|
this.configSet.length = 0
|
||||||
|
this.nextVariants = async (input) => await this.nextObjects(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = end
|
28
lib/components/TopologyBrowser/commands/fdn.js
Normal file
28
lib/components/TopologyBrowser/commands/fdn.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
20
lib/components/TopologyBrowser/commands/get.js
Normal file
20
lib/components/TopologyBrowser/commands/get.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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
|
8
lib/components/TopologyBrowser/commands/home.js
Normal file
8
lib/components/TopologyBrowser/commands/home.js
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
function home() {
|
||||||
|
this.nextPoId = this.poIds.shift()
|
||||||
|
this.poIds.length = 0
|
||||||
|
this.nextVariants = async (input) => await this.nextObjects(input)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = home
|
23
lib/components/TopologyBrowser/commands/initialPrompt.js
Normal file
23
lib/components/TopologyBrowser/commands/initialPrompt.js
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
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}`
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = initialPrompt
|
14
lib/components/TopologyBrowser/commands/nextAttributes.js
Normal file
14
lib/components/TopologyBrowser/commands/nextAttributes.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
34
lib/components/TopologyBrowser/commands/nextObjects.js
Normal file
34
lib/components/TopologyBrowser/commands/nextObjects.js
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
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
|
28
lib/components/TopologyBrowser/commands/set.js
Normal file
28
lib/components/TopologyBrowser/commands/set.js
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
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
|
14
lib/components/TopologyBrowser/commands/setAttribute.js
Normal file
14
lib/components/TopologyBrowser/commands/setAttribute.js
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
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
|
11
lib/components/TopologyBrowser/commands/setIdByCommand.js
Normal file
11
lib/components/TopologyBrowser/commands/setIdByCommand.js
Normal file
@@ -0,0 +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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = setIdByCommand
|
20
lib/components/TopologyBrowser/commands/show.js
Normal file
20
lib/components/TopologyBrowser/commands/show.js
Normal file
@@ -0,0 +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)))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = show
|
30
lib/components/TopologyBrowser/commands/sync.js
Normal file
30
lib/components/TopologyBrowser/commands/sync.js
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
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
|
12
lib/components/TopologyBrowser/commands/up.js
Normal file
12
lib/components/TopologyBrowser/commands/up.js
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
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
|
136
lib/components/TopologyBrowser/inputHandler.js
Normal file
136
lib/components/TopologyBrowser/inputHandler.js
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
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
|
156
lib/components/TopologyBrowser/inputValue.js
Normal file
156
lib/components/TopologyBrowser/inputValue.js
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
const colors = require('colors')
|
||||||
|
const inquirer = require('inquirer')
|
||||||
|
const banner = require('../util/banner')
|
||||||
|
const { isValidNumber, isValidString, checkValueRangeConstraints } = require('../util/validation')
|
||||||
|
|
||||||
|
|
||||||
|
async function inputInteger(attributeData) {
|
||||||
|
const message = `${attributeData.key.yellow} { ${attributeData.unit ? attributeData.unit : 'parrots'.gray} } (${attributeData.type}): `
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'value',
|
||||||
|
suffix: '?'.green,
|
||||||
|
message,
|
||||||
|
default: attributeData.defaultValue,
|
||||||
|
validate: input => isValidNumber(input, attributeData.constraints),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return +input.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputEnumRef(attributeData) {
|
||||||
|
const message = `Select Value For ${attributeData.key.yellow}: `
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'value',
|
||||||
|
suffix: '?'.green,
|
||||||
|
message,
|
||||||
|
choices: attributeData.enumeration.enumMembers.map(item => ({ name: `${item.key} (${item.description})`, value: item.key, short: item.key })),
|
||||||
|
default: attributeData.defaultValue,
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return input.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputBoolean(attributeData) {
|
||||||
|
const variants = ['true', 'false']
|
||||||
|
if (attributeData.constraints.nullable) variants.push('null')
|
||||||
|
const message = `Select Value For ${attributeData.key.yellow}:`
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'list',
|
||||||
|
name: 'value',
|
||||||
|
suffix: '?'.green,
|
||||||
|
message,
|
||||||
|
choices: variants,
|
||||||
|
default: String(attributeData.defaultValue),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return {
|
||||||
|
true: true,
|
||||||
|
false: false,
|
||||||
|
null: null
|
||||||
|
}[input.value]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputString(attributeData) {
|
||||||
|
const message = `${attributeData.key.yellow} (${attributeData.type}): `
|
||||||
|
const input = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'input',
|
||||||
|
name: 'value',
|
||||||
|
suffix: '?'.green,
|
||||||
|
message,
|
||||||
|
default: attributeData.defaultValue,
|
||||||
|
validate: input => isValidString(input, attributeData.constraints),
|
||||||
|
}
|
||||||
|
])
|
||||||
|
return input.value
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputList(attributeData) {
|
||||||
|
const message = `${attributeData.key.yellow} List Of (${attributeData.listReference.type}) Size: `
|
||||||
|
const result = []
|
||||||
|
const { value } = await inquirer.prompt([
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
name: 'value',
|
||||||
|
suffix: '?'.green,
|
||||||
|
message,
|
||||||
|
default: 'null',
|
||||||
|
validate: input => +input > 0 || input === 'null',
|
||||||
|
}
|
||||||
|
])
|
||||||
|
if (value === 'null') return null
|
||||||
|
const checkResult = checkValueRangeConstraints(value, attributeData.constraints)
|
||||||
|
if (checkResult) return checkResult
|
||||||
|
if (attributeData.constraints.orderingConstraint) banner(attributeData)
|
||||||
|
await inputListValues(attributeData, value, result)
|
||||||
|
return result.value ? result.value : result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputListValues(attributeData, listSize, result) {
|
||||||
|
for (let i = 0; i < listSize; i++) {
|
||||||
|
let input
|
||||||
|
if (!attributeData.listReference.key) {
|
||||||
|
attributeData.listReference.key = `${attributeData.key} [${i}]`
|
||||||
|
input = await inputByType(attributeData.listReference)
|
||||||
|
attributeData.listReference.key = null
|
||||||
|
} else {
|
||||||
|
input = await inputByType(attributeData.listReference)
|
||||||
|
}
|
||||||
|
if (attributeData.constraints.uniqueMembers) {
|
||||||
|
if (result.indexOf(input.value) !== -1) {
|
||||||
|
console.log('>>Array Values Should Be Unique'.red)
|
||||||
|
i--
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result.push(input.value)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputComplexRef(attributeData) {
|
||||||
|
const result = []
|
||||||
|
const attributes = attributeData.complexRef.attributes.slice()
|
||||||
|
while (attributes.length > 0) {
|
||||||
|
const item = attributes.shift()
|
||||||
|
const { value } = await inputByType(item)
|
||||||
|
result.push({
|
||||||
|
key: item.key,
|
||||||
|
value,
|
||||||
|
datatype: item.type
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
async function inputByType(typeReference) {
|
||||||
|
const inputs = {
|
||||||
|
INTEGER: inputInteger,
|
||||||
|
SHORT: inputInteger,
|
||||||
|
ENUM_REF: inputEnumRef,
|
||||||
|
BOOLEAN: inputBoolean,
|
||||||
|
STRING: inputString,
|
||||||
|
MO_REF: inputString,
|
||||||
|
LIST: inputList,
|
||||||
|
COMPLEX_REF: inputComplexRef
|
||||||
|
}
|
||||||
|
if (inputs[typeReference.type]) {
|
||||||
|
const input = await inputs[typeReference.type](typeReference)
|
||||||
|
return { value: input }
|
||||||
|
}
|
||||||
|
banner(typeReference)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = inputByType
|
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +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"
|
||||||
|
}
|
||||||
|
}
|
35
util/SpinnerWithCounter.js
Normal file
35
util/SpinnerWithCounter.js
Normal file
@@ -0,0 +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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = SpinnerWithCounter
|
17
util/createNext.js
Normal file
17
util/createNext.js
Normal file
@@ -0,0 +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,
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = createNext
|
29
util/logError.js
Normal file
29
util/logError.js
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
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
|
49
util/logNode.js
Normal file
49
util/logNode.js
Normal file
@@ -0,0 +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}`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = { logNodeStatus, logNodeProperties }
|
36
util/logProject.js
Normal file
36
util/logProject.js
Normal file
@@ -0,0 +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}
|
||||||
|
`)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
module.exports = logProject
|
10
util/validation.js
Normal file
10
util/validation.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
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 }
|
Reference in New Issue
Block a user