bulk import creation

This commit is contained in:
Vyacheslav.Sviridov
2022-06-26 21:09:48 +06:00
parent 91b643463f
commit bcc0a42f73
25 changed files with 813 additions and 86 deletions

View File

@@ -11,6 +11,7 @@ require('dotenv').config({ path: [...__dirname.split(path.sep).slice(0,-1), '.en
const AutoProvisioning = require('../lib/applications/AutoProvisioning/AutoProvisioning')
const TopologyBrowser = require('../lib/applications/TopologyBrowser/TopologyBrowser')
const BulkImport = require('../lib/applications/BulkImport/BulkImport')
const logError = require('../util/logError')
@@ -30,7 +31,12 @@ const applications = [
id: 'prvn',
appClass: AutoProvisioning,
name: 'Auto Provisioning',
}
},
{
id: 'bulk',
appClass: BulkImport,
name: 'Bulk Import',
},
]
const appIds = applications.map(item => item.id)

View File

@@ -20,9 +20,11 @@ class AutoProvisioning extends ENM {
this.appUrl = '/auto-provisioning/v1'
this.projects = null
this.projectIndex = -1
this.projectId = null
// this.projectIndex = -1
this.nodes = null
this.nodeIndex = -1
// this.nodeIndex = -1
this.nodeId = null
this.prompt = ''
this.help = 'No results...'
}
@@ -43,6 +45,10 @@ class AutoProvisioning extends ENM {
await deleteProject.call(this)
}
async deleteNode() {
// await deleteNode.call(this)
}
async getNode() {
return await getNode.call(this)
}

View File

@@ -9,22 +9,19 @@ 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()
}
if (prvn.nodes) {
prvn.nodeId = command
prvn.getNode()
} else {
prvn.projectId = command
await prvn.getProjectData()
}
}
async function handleCommand(prvn, command) {
const [, cmd] = command.match(/\[(\w+)\]/) || [, command]
switch (cmd) {
// const [, cmd] = command.match(/\[(\w+)\]/) || [, command]
switch (command) {
case 'exit':
prvn.prompt = ''
@@ -36,7 +33,7 @@ async function handleCommand(prvn, command) {
prvn.nodeIndex ? await prvn.getProjects() : await prvn.getProjectData()
break
case 'delete':
prvn.nodeIndex ? prvn.deleteNode() : await prvn.deleteProject()
prvn.nodeIndex ? await prvn.deleteNode() : await prvn.deleteProject()
break
case 'status':

View File

@@ -30,22 +30,22 @@ const nodeCommandsHelp = [
async function getNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// 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}) `
this.prompt = `${this.projectId} (${this.nodeId}) `
}
async function getNodeStatus() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s status...`,
text: `Getting ${this.nodeId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}`
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}`
}
const { data: { statusEntries } } = await this.httpClient.request(axiosConfig)
logNodeStatus(statusEntries)
@@ -53,12 +53,12 @@ async function getNodeStatus() {
async function getNodeProperties() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Getting ${nodeId}'s properties...`,
text: `Getting ${this.nodeId}'s properties...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}?filter=properties`
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}?filter=properties`
}
const { data: { attributes, attributeGroups } } = await this.httpClient.request(axiosConfig)
logNodeProperties(attributes, attributeGroups)
@@ -75,12 +75,12 @@ async function bindNode() {
validate: input => isValidHardwareId(input),
}
])
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Binding ${hardwareId} to ${nodeId}...`,
text: `Binding ${hardwareId} to ${this.nodeId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/bind`,
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}/actions/bind`,
data: {
hardwareId,
},
@@ -91,12 +91,12 @@ async function bindNode() {
async function cancelNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Canceling ${nodeId}...`,
text: `Canceling ${this.nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/cancel`,
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}/actions/cancel`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Canceling ${statusText}`))
@@ -104,12 +104,12 @@ async function cancelNode() {
async function resumeNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const axiosConfig = {
text: `Resuming ${nodeId}...`,
text: `Resuming ${this.nodeId}...`,
method: 'post',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/actions/resume`,
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}/actions/resume`,
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Resuming ${statusText}`))
@@ -117,8 +117,8 @@ async function resumeNode() {
async function configurationsNode() {
const { projectId } = this.projects[this.projectIndex]
const nodeId = this.nodes[this.nodeIndex]
// const { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const fileNameInput = await inquirer.prompt([{
type: 'file-tree-selection',
name: 'nodeFile',
@@ -127,9 +127,9 @@ async function configurationsNode() {
const formData = new FormData()
formData.append('file', fs.createReadStream(fileNameInput.nodeFile))
const axiosConfig = {
text: `Uploading ${nodeId} configuration to ${projectId}...`,
text: `Uploading ${this.nodeId} configuration to ${this.projectId}...`,
method: 'put',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations`,
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}/configurations`,
headers: formData.getHeaders(),
data: formData
}
@@ -139,13 +139,13 @@ async function configurationsNode() {
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 { projectId } = this.projects[this.projectIndex]
// const nodeId = this.nodes[this.nodeIndex]
const saveFileName = path.join(process.cwd(), `Site_Install_${this.nodeId}.xml`)
const axiosConfig = {
text: `Downloading site install file ${nodeId}...`,
text: `Downloading site install file ${this.nodeId}...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}/nodes/${nodeId}/configurations/siteinstall`,
url: `${this.appUrl}/projects/${this.projectId}/nodes/${this.nodeId}/configurations/siteinstall`,
}
const { data } = await this.httpClient.request(axiosConfig)
await fsPromises.writeFile(saveFileName, data)

View File

@@ -39,14 +39,14 @@ async function getProjects() {
async function getProjectData() {
const { projectId } = this.projects[this.projectIndex]
// const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Getting ${projectId}'s status...`,
text: `Getting ${this.projectId}'s status...`,
method: 'get',
url: `${this.appUrl}/projects/${projectId}`
url: `${this.appUrl}/projects/${this.projectId}`
}
const { data: { nodeSummary } } = await this.httpClient.request(axiosConfig)
axiosConfig.text = `Getting ${projectId}'s properties...`
axiosConfig.text = `Getting ${this.projectId}'s properties...`
axiosConfig.url += '?filter=properties'
const { data } = await this.httpClient.request(axiosConfig)
logProject(data, nodeSummary)
@@ -55,7 +55,7 @@ async function getProjectData() {
this.commands = projectCommands
this.help = projectCommandsHelp.join('\n ')
this.nodeIndex = -1
this.prompt = projectId
this.prompt = this.projectId
}
@@ -81,11 +81,11 @@ async function newProject() {
async function deleteProject() {
const { projectId } = this.projects[this.projectIndex]
// const { projectId } = this.projects[this.projectIndex]
const axiosConfig = {
text: `Deleting project ${projectId}...`,
text: `Deleting project ${this.projectId}...`,
method: 'delete',
url: `${this.appUrl}/projects/${projectId}`
url: `${this.appUrl}/projects/${this.projectId}`
}
const { statusText } = await this.httpClient.request(axiosConfig)
console.log(chalk.bgGreen(`Delete ${statusText}`))
@@ -114,7 +114,11 @@ function projectsChoices(projects) {
failed && failed + '⛔',
]
const space = ' '.repeat(Math.max(14, projectId.length + 2) - projectId.length)
return `${chalk.bold(projectId)}${space}(${numberOfNodes}) ${statusList.filter(item => item).join(' ')}`
return {
value: projectId,
short: projectId,
name: `${chalk.bold(projectId)}${space}(${numberOfNodes}) ${statusList.filter(item => item).join(' ')}`,
}
})
}

View File

@@ -0,0 +1,81 @@
const chalk = require('chalk')
const ENM = require('../../components/ENM')
const {
newJob,
getJobs,
pageJobs,
} = require('./jobs')
const {
job,
deleteJob,
} = require('./job')
const { getOperations } = require('./operations')
const inputHandler = require('./inputHandler')
const createNext = require('../../../util/createNext')
class BulkImport extends ENM {
constructor(username, password, url) {
super(username, password, url)
this.appUrl = '/bulk-configuration/v1/import-jobs/jobs'
this.jobs = null
this.jobsLinks = null
this.jobId = null
this.username = username
this.onlyMy = false
this.jobsOffset = 0
this.jobsLimit = 50
this.operations = null
this.operationsId = null
this.operationsOffset = 0
this.operationsLimit = 50
this.operationsLinks = null
this.prompt = ''
this.help = 'No results...'
}
async getJobs() {
await getJobs.call(this)
}
async pageJobs(page) {
await pageJobs.call(this, page)
}
async deleteJob() {
await deleteJob.call(this)
}
job(jobId) {
job.call(this, jobId)
}
async my(){
this.onlyMy = !this.onlyMy
console.log(chalk.yellowBright(`Show ${this.onlyMy ? 'only my' : 'all'} jobs❗`))
this.jobsOffset = 0
this.jobsLimit = 50
await this.getJobs()
}
async newJob() {
await newJob.call(this)
}
async getOperations() {
await getOperations.call(this)
}
async next(input) {
return createNext.call(this, input ? input : '')
}
async inputHandler() {
await inputHandler.call(this)
}
}
module.exports = BulkImport

View File

@@ -0,0 +1,105 @@
const inquirer = require('inquirer')
const chalk = require('chalk')
const logError = require('../../../util/logError')
const { logOperation } = require('../../../util/logOperation')
const { isEmpty } = require('../../../util/validation')
inquirer.registerPrompt('autocomplete', require('inquirer-autocomplete-prompt'))
async function commandOther(bulk, command) {
if (bulk.operations) {
const operation = bulk.operations.find(i => i.id === command)
logOperation(operation)
}
if (!bulk.jobId) {
bulk.job(command)
}
}
async function handleCommand(bulk, command) {
switch (command) {
case 'exit':
bulk.prompt = ''
break
case 'next':
case 'last':
case 'first':
case 'prev':
await bulk.pageJobs(command)
break
case 'operations':
case 'failures':
await bulk.getOperations()
break
case 'my':
await bulk.my()
break
case 'new':
await bulk.newJob()
break
case 'back':
bulk.operations ? await bulk.job() : await bulk.getJobs()
break
case 'delete':
await bulk.deleteJob()
break
// case 'status':
// await bulk.getNodeStatus()
// break
// case 'properties':
// await bulk.getNodeProperties()
// break
// case 'bind':
// await bulk.bindNode()
// break
// case 'cancel':
// await bulk.cancelNode()
// break
// case 'resume':
// await bulk.resumeNode()
// break
// case 'configurations':
// await bulk.configurationsNode()
// break
// case 'siteinstall':
// await bulk.siteinstallNode()
// break
default:
await commandOther(bulk, command)
}
}
async function inputHandler() {
await this.getJobs()
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

@@ -0,0 +1,68 @@
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 { logJob } = require('../../../util/logJob')
inquirer.registerPrompt('file-tree-selection', inquirerFileTreeSelection)
const jobCommands = ['operations', 'failures', 'delete', 'back', 'exit']
const jobCommandsHelp = [
'[delete] - Deletes import job.',
'[back] - Return to jobs.',
'[exit] - Exit this app.',
'Or choose a node from list...',
]
function job(jobId) {
const job = this.jobs.find(job => job.id === jobId)
if (!job) {
throw new Error('Job not Found❗')
}
this.jobId = jobId
this.operations = null
logJob(job)
this.commands = [...jobCommands]
this.help = jobCommandsHelp.join('\n ')
this.choices = []
this.prompt = job.name
}
async function deleteJob() {
if (!this.jobId) {
throw new Error('Job Is not selected❗')
}
const { confirm } = await inquirer.prompt([
{
type: 'confirm',
name: 'confirm',
message: 'Still want to delete❓',
default: true,
},
])
if (!confirm) return
const axiosConfig = {
text: `Deleting job ${this.jobId} ...`,
method: 'delete',
url: this.appUrl,
params: {
jobId: this.jobId,
}
}
const { data } = await this.httpClient.request(axiosConfig)
data.errors.forEach(err => {
console.log(`${err.type === 'INFO' ? chalk.green(err.message) : chalk.red(err.code + ': ' + err.message)}`)
})
await this.getJobs()
}
module.exports = {
job,
deleteJob,
}

View File

@@ -0,0 +1,173 @@
const chalk = require('chalk')
const inquirer = require('inquirer')
const { statusPic } = require('../../../util/logJob')
const { isEmpty } = require('../../../util/validation')
const jobsCommands = ['new', 'my', 'exit']
const jobsCommandsHelp = [
'[new] - Creates a new import job.',
'[exit] - Exit this app.',
'Or choose a job from list...',
]
async function newJob() {
const {
name,
validationPolicy,
executionPolicy,
unsynchNodes,
executionOrder
} = await inquirer.prompt([
{
type: 'input',
name: 'name',
message: 'Type a job\'s name:',
validate: isEmpty,
},
{
type: 'checkbox',
name: 'validationPolicy',
message: 'Validation Policy:',
choices: [
{
name: 'Skip MO Instance Validation',
value: 'no-instance-validation',
},
{
name: 'Skip Node Based Validation',
value: 'no-node-based-validation',
checked: true,
},
],
},
{
type: 'list',
name: 'executionPolicy',
message: 'Execution Error Handling:',
choices: [
{
name: 'Stop',
value: 'stop-on-error',
},
{
name: 'Skip to next node',
value: 'continue-on-error-node',
},
{
name: 'Skip to next operation',
value: 'continue-on-error-operation',
},
],
default: 'stop-on-error',
},
{
type: 'list',
name: 'unsynchNodes',
message: 'Unsynchronized Nodes Policy:',
choices: [
{
name: 'Import skips operation execution on unsynchronized nodes. Recommended for faster performance.',
value: 'skip-unsync-nodes',
},
{
name: 'Import will be agnostic towards node synchronization state, implying import attempts operation execution on unsychronized nodes as well.',
value: 'exec-unsync-nodes',
},
],
default: 'skip-unsync-nodes',
},
{
type: 'list',
name: 'executionOrder',
message: 'Execution Order:',
choices: [
{
name: 'Sequential',
value: 'sequential',
},
{
name: 'Parallel',
value: 'parallel',
},
],
default: 'parallel',
when: input => !input.executionPolicy.includes('stop-on-error')
},
])
const axiosConfig = {
text: 'Creating job...',
method: 'post',
url: this.appUrl,
data: {
name,
validationPolicy: validationPolicy.length
? validationPolicy
: ['instance-validation', 'node-based-validation'],
executionPolicy: [
executionPolicy,
unsynchNodes,
executionOrder ?? 'sequential',
]
},
}
const { data } = await this.httpClient.request(axiosConfig)
this.jobs.unshift(data)
this.job(data.id)
}
async function getJobs() {
const params =new URLSearchParams()
params.append('offset', this.jobsOffset)
params.append('limit', this.jobsLimit)
params.append('expand', 'summary')
params.append('expand', 'files')
if (this.onlyMy) {
params.append('createdBy', this.username)
}
const axiosConfig = {
text: 'Getting jobs...',
method: 'get',
url: this.appUrl,
params,
}
const { data } = await this.httpClient.request(axiosConfig)
this.jobs = data.jobs
this.jobId = null
this.jobsLinks = data._links
this.choices = this.jobs.map(item => ({
name: `${statusPic(item.status)} ${item.name} ${chalk.dim(item.userId)}`,
value: item.id,
short: item.id
}))
this.commands = [...jobsCommands]
Object.values(this.jobsLinks).forEach(item => item.rel !== 'self' && this.commands.push(item.rel))
this.help = jobsCommandsHelp.join('\n ')
this.prompt = `${data.totalCount} jobs (${this.jobsOffset}-${Math.min(this.jobsOffset + this.jobsLimit, data.totalCount)})`
}
function setJobsPagination(href) {
const url = new URL(href)
const searchParams = new URLSearchParams(url.search)
this.jobsLimit = +searchParams.get('limit')
this.jobsOffset = +searchParams.get('offset')
}
async function pageJobs(page) {
if (!this.jobsLinks[page]) {
throw new Error(`No ${page} jobs❗`)
}
setJobsPagination.call(this, this.jobsLinks[page].href)
await this.getJobs()
}
module.exports = {
newJob,
getJobs,
pageJobs,
}

View File

@@ -0,0 +1,68 @@
const chalk = require("chalk")
const operationsCommands = ['print', 'back', 'exit']
const operationsCommandsHelp = [
'[print] - Print all operations.',
'[back] - Return to jobs.',
'[exit] - Exit this app.',
'Or choose a node from list...',
]
async function getOperations() {
const params =new URLSearchParams()
params.append('offset', this.operationsOffset)
params.append('limit', this.operationsLimit)
params.append('expand', 'attributes')
params.append('expand', 'persistentCurrentAttributes')
params.append('expand', 'failures')
params.append('expand', 'warnings')
// params.append('status', 'invalid')
// params.append('status', 'execution-error')
const axiosConfig = {
text: 'Getting operations...',
method: 'get',
url: `${this.appUrl}/${this.jobId}/operations/`,
params,
}
const { data } = await this.httpClient.request(axiosConfig)
this.operations = data.operations
this.operationsId = null
this.operationsLinks = data._links
this.choices = this.operations.map(item => ({
name: `${item.status === 'EXECUTED' ? chalk.green(item.type) : chalk.red(item.type)} ${item.fdn.slice(-79)}`,
value: item.id,
short: item.id
}))
this.commands = [...operationsCommands]
Object.values(this.operationsLinks).forEach(item => {
if (item.rel !== 'self' || item.rel !== 'job') {
this.commands.push(item.rel)
}
})
this.help = operationsCommandsHelp.join('\n ')
this.prompt = `${data.totalCount} operations (${this.operationsOffset}-${Math.min(this.operationsOffset + this.operationsLimit, data.totalCount)})`
}
function setOperationsPagination(href) {
const url = new URL(href)
const searchParams = new URLSearchParams(url.search)
this.operationsLimit = +searchParams.get('limit')
this.operationsOffset = +searchParams.get('offset')
}
async function pageOperations(page) {
if (!this.operationsLinks[page]) {
throw new Error(`No ${page} jobs❗`)
}
setOperationsPagination.call(this, this.operationsLinks[page].href)
await this.getOperations()
}
module.exports = {
getOperations,
pageOperations,
}

View File

@@ -110,7 +110,7 @@ class TopologyBrowser extends ENM {
}
check() {
logAttributes(this.fdn.replace(/\((\w+)\)/g, ''), this.configSet)
logAttributes(this.fdn, this.configSet)
}
home() {

View File

@@ -13,11 +13,11 @@ function get() {
if (!attribute) {
throw new Error(`Attribute not Found: ${this.attribute}`)
}
const attributeData = this.attributesData.find(item => item.key === this.attribute)
const { constraints, defaultValue, type } = 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'] : ''}
console.log(` ${chalk.green('Type: ') + type} ${defaultValue && chalk.yellow('Default: ') + defaultValue}
`)
if (attributeData.constraints && attributeData.constraints.orderingConstraint) banner(attributeData)
if (constraints && constraints.orderingConstraint) banner(constraints)
}
module.exports = get

View File

@@ -26,4 +26,6 @@ async function goToFdn(targetFdn) {
}
this.fdn = fdn
}
module.exports = goToFdn

View File

@@ -1,13 +1,18 @@
const chalk = require('chalk')
async function nextAttributes(input) {
const filter = input ? input : ''
// const filter = input ? input : ''
this.commands = this.configCommands
.filter(item => item.toLowerCase().includes(filter.toLowerCase()))
// .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)
.map(({ key, value }) => ({
value: key,
short: key,
name: `${key} ${chalk.dim(typeof value !== 'object' || value === null ? value : '...')}`
}))
// .filter(item => item.name.toLowerCase().includes(filter.toLowerCase()))
.sort((a, b) => a.value > b.value ? 1 : -1)
}

View File

@@ -40,17 +40,21 @@ async function nextObjects(input) {
this.poIds.push(this.currentPoId)
await networkRequest.call(this)
}
this.commands = otherCommands.filter(cmd => cmd.toLowerCase().includes(filter.toLowerCase()))
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}`
return {
name: `${child.moType}=${child.moName}${st}${rt}${ne}`,
value: `${child.moType}=${child.moName}`,
short: `${child.moType}=${child.moName}`,
}
})
.filter(child => child.toLowerCase().includes(filter.toLowerCase()))
.concat(filter.startsWith('show') ? [filter] : [])
.concat(filter.startsWith('fdn') ? [filter] : [])
// .filter(child => child.name.toLowerCase().includes(filter.toLowerCase()))
.concat(filter.startsWith('show') ? [{ name: filter, value: filter, short: filter }] : [])
.concat(filter.startsWith('fdn') ? [{ name: filter, value: filter, short: filter }] : [])
}

View File

@@ -21,15 +21,15 @@ function commandOther(tplg, command) {
async function handleCommand(tplg, command) {
const [cmd, param] = command.split(/\s+/)
const cmdMatch = cmd.match(/\[(\w+)\]/)
const cmdName = cmdMatch ? cmdMatch[1] : cmd
switch (cmdName) {
const [cmd, param = ''] = command.split(/\s+/)
// const cmdMatch = cmd.match(/\[(\w+)\]/)
// const cmdName = cmdMatch ? cmdMatch[1] : cmd
switch (cmd) {
case 'exit':
tplg.fdn = ''
break
case 'show':
await tplg.show(param ? param.trim() : '')
await tplg.show(param)
break
case 'config':
await tplg.config()
@@ -65,7 +65,7 @@ async function handleCommand(tplg, command) {
tplg.persistent()
break
case 'fdn':
await tplg.goToFdn(param ? param.trim() : '')
await tplg.goToFdn(param)
break
case 'alarms':
await tplg.alarms()
@@ -91,7 +91,7 @@ async function inputHandler() {
message: chalk.blue(this.getPrompt()),
pageSize: 10,
prefix: '',
suffix: this.isConfig ? chalk.blue('#') : chalk.blue('>'),
suffix: chalk.blue(this.isConfig ? '#': '>'),
validate: isEmpty,
source: async (answers, input) => await this.next(input),
emptyText: this.help,

View File

@@ -4,8 +4,8 @@ class ENM {
constructor(username, password, url) {
this.logoutUrl = '/logout'
this.loginUrl = `/login?IDToken1=${username}&IDToken2=${password}`
this.commands = null
this.choices = null
this.commands = []
this.choices = []
this.httpClient = axiosHttpClient(url)
}

View File

@@ -1,4 +1,8 @@
const ora = require('ora')
const { isXMas } = require('../../util/validation')
const defaultSpinner = process.env.SPINNER || 'clock'
class SpinnerWithCounter {
constructor() {
@@ -9,6 +13,7 @@ class SpinnerWithCounter {
start(text) {
if (!this.spinner) {
this.spinner = ora(text)
this.spinner.spinner = isXMas() ? 'christmas' : defaultSpinner
this.spinner.start()
}
this.counter = ++this.counter

View File

@@ -4,16 +4,21 @@ 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()))
const choices = this.choices.filter(choice => choice.name.toLowerCase().includes(filter.toLowerCase()))
let result = [
...choices,
separator,
...commands.map(cmd => `[${cmd}]`),
...commands.map(cmd => ({
name: `[${cmd}]`,
value: cmd,
short: cmd,
})),
separator,
]
if (result.includes([`${filter}`])) {
result = result.filter(item => item !== [`${filter}`])
result.unshift([`${filter}`])
const findFilter = result.find(item => item.value === filter)
if (findFilter) {
result = result.filter(item => item.value !== filter)
result.unshift(findFilter)
}
return result
}

View File

@@ -26,7 +26,7 @@ function colorize(attributes) {
function logAttributes(fdn, attributes) {
const output = `
${chalk.yellow.bold('FDN')}: ${chalk.bold(fdn)}
${chalk.yellow.bold('FDN')}: ${chalk.bold(fdn.replace(/\((\w+)\)/g, ''))}
${colorize(attributes)}`
console.log(output)
}

View File

@@ -1,6 +1,7 @@
const chalk = require('chalk')
function logError(err) {
// console.dir(err)
try {
if (!err.response) {
const {
@@ -39,6 +40,17 @@ function logError(err) {
errorBody = data.body
errorDetails = data.detail
}
//bulk error
if (data.errors) {
errorBody = `Total Errors Count: ${data.totalCount}`
errorDetails = data.errors.map(err => {
return `
${err.type}: ${err.code}
${err.message}
${err.parameters && typeof err.parameters === 'object' ? JSON.stringify(err.parameters) : err.parameters}
`
})
}
console.log(`
⚠️ ${chalk.bold.bgRed(errorTitle)}
${chalk.yellow(errorBody)}${errorDetails ? '\n' + errorDetails.toString() : ''}

113
util/logJob.js Executable file
View File

@@ -0,0 +1,113 @@
const chalk = require('chalk')
function statusPic(status) {
switch (status) {
case 'CREATED':
return '📄'
case 'PARSING':
return '📂'
case 'PARSED':
return '📁'
case 'VALIDATING':
return '🎛'
case 'VALIDATED':
return '📊'
case 'EXECUTING':
return '⏳'
case 'EXECUTED':
return '✅'
case 'CANCELLING':
return '❌'
case 'CANCELLED':
return '⛔️'
default:
return '❓'
}
}
function statusColor(status) {
switch (status) {
case 'EXECUTED':
return chalk.green(status)
case 'CANCELLED':
return chalk.red(status)
default:
return chalk.yellow(status)
}
}
function validationPolicy(job) {
return [
`${job.validationPolicy.includes('instance-validation') ? '✅' : '❌'} Instance Validation`,
`${job.validationPolicy.includes('node-based-validation') ? '✅' : '❌'} Node Based Validation`,
]
}
function executionPolicy(job) {
return [
`${job.executionPolicy.includes('stop-on-error') ? '✅' : '❌'} Stop`,
`${job.executionPolicy.includes('continue-on-error-node') ? '✅' : '❌'} Skip to Next Node`,
`${job.executionPolicy.includes('continue-on-error-operation') ? '✅' : '❌'} Skip to Next Operation`,
`${job.executionPolicy.includes('parallel') ? '✅ Parallel' : '✅ Sequential'}`,
`${job.executionPolicy.includes('skip-unsync-nodes') ? '✅' : '❌'} Skip Unsync Nodes`,
]
}
function timeAttributes(job) {
Object.entries(job)
.forEach(([key, value]) => {
if (['created', 'lastValidation', 'lastExecution'].includes(key)) {
const title = key.replace(/([A-Z])/g, ' $1').replace(/^./, c => c.toUpperCase())
const formattingSpaces = ' '.repeat(18 - title.length)
console.log(` ${chalk.yellowBright.bold(title + ':')}${formattingSpaces} ${new Date(value).toLocaleString()}`)
}
})
}
function optionalAttributes(job) {
if (job.failureReason) {
console.log(` ${chalk.yellowBright.bold('Failure Reason: ')} ${chalk.red(job.failureReason)}`)
}
if (job.files && job.files.find(i => i).name) {
console.log(` ${chalk.yellowBright.bold('Files: ')}
${job.files.map(f => `${chalk.dim.italic(f.format)} ${f.name}`).join('\n ')}`)
}
if (job.summary) {
console.table(job.summary, ['parsed', 'valid', 'invalid', 'executed', 'executionErrors'])
}
}
function logJob(job) {
if (!job) {
throw new Error('No job data❗')
}
console.log(`
${chalk.yellowBright.bold('Job ID: ')} ${chalk.dim(job.id)}
${chalk.yellowBright.bold('Job Name: ')} ${job.name}
${chalk.yellowBright.bold('User: ')} ${chalk.cyanBright.underline(job.userId)}
${chalk.yellowBright.bold('Configuration: ')} ${chalk.greenBright(job.configuration)}
${chalk.yellowBright.bold('Status: ')} ${statusColor(job.status)} ${statusPic(job.status)}
${chalk.yellowBright.bold('Time Total: ')} ${new Date(job.totalElapsedTime * 1000).toISOString().slice(11, -5)}`)
timeAttributes(job)
console.log(` ${chalk.yellowBright.bold('Validation Options:')}
${chalk.gray(validationPolicy(job).join('\n '))}
${chalk.yellowBright.bold('Execution Options:')}
${chalk.gray(executionPolicy(job).join('\n '))}`)
optionalAttributes(job)
console.log('')
}
module.exports = {
logJob,
statusPic,
}

64
util/logOperation.js Executable file
View File

@@ -0,0 +1,64 @@
const chalk = require('chalk')
function statusColor(status) {
switch (status) {
case 'EXECUTED':
return chalk.green(status)
case 'INVALID':
return chalk.red(status)
default:
return chalk.yellow(status)
}
}
function attributesTransform(attributes, currentAttributes) {
return attributes.reduce((prev, curr) => {
const { currentValue } = currentAttributes.find(i => i.name === curr.name) ?? {}
const { name, suppliedValue } = curr
return {
...prev,
[name]: {
currentValue: currentValue ? currentValue : '',
suppliedValue,
}
}
}, {})
}
function logOperation(operation) {
if (!operation) {
throw new Error('No operation data❗')
}
const {
id,
type,
status,
updateTime,
fdn,
failures,
attributes = [],
currentAttributes = [],
} = operation
console.log(`
${chalk.yellowBright.bold('Operation ID:')} ${chalk.dim(id)}
${chalk.yellowBright.bold('Type: ')} ${type}
${chalk.yellowBright.bold('Status: ')} ${statusColor(status)}
${chalk.yellowBright.bold('Update Time: ')} ${new Date(updateTime).toLocaleString()}
${chalk.yellowBright.bold('FDN: ')} ${chalk.cyanBright.underline(fdn)}`)
if (failures) {
console.log(` ${chalk.yellowBright.bold('Failures: ')}`)
failures.forEach(({ failureReason }) => console.log(`${chalk.redBright(failureReason)}`))
}
if (attributes.length > 0) {
console.table(attributesTransform(attributes, currentAttributes))
}
}
module.exports = {
logOperation,
}

View File

@@ -3,7 +3,7 @@ const chalk = require('chalk')
function logProject(data, nodeSummary) {
if (!data || !nodeSummary) {
throw new Error('No project data or node summary!')
throw new Error('No project data or node summary')
}
const {
id: projectId,

View File

@@ -53,6 +53,14 @@ const isValidNodeName = (input) => {
}
const isXMas = () => {
const date = new Date()
if (date.getMonth() === 11 || date.getMonth() === 0) {
return true
}
}
module.exports = {
isEmpty,
isValidHardwareId,
@@ -60,4 +68,5 @@ module.exports = {
isValidString,
checkValueRangeConstraints,
isValidNodeName,
isXMas,
}