cisco-axl 1.0.1 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -25
- package/examples/change_phone_model/changePhoneModel.js +159 -0
- package/examples/copy_phone/copyPhone.js +39 -0
- package/examples/copy_sip_trunk/README.md +3 -0
- package/examples/copy_sip_trunk/copySipTrunk.js +54 -0
- package/examples/getTagsForOperation.js +15 -0
- package/examples/sql/README.md +7 -0
- package/examples/sql/cfa.sql +14 -0
- package/examples/sql/mask.sql +7 -0
- package/examples/sql/max_busy.sql +14 -0
- package/examples/sql/sqlQuery.js +39 -0
- package/examples/sql/unassignedDn.sql +25 -0
- package/examples/templates/README.md +11 -0
- package/examples/templates/add_phone_update_line/addPhoneUpdateLine.js +49 -0
- package/examples/templates/add_phone_update_line/lineTemplate.json +12 -0
- package/examples/templates/add_phone_update_line/phoneTemplate.json +151 -0
- package/examples/templates/update_phone_update_line/lineTemplate.json +12 -0
- package/examples/templates/update_phone_update_line/phoneTemplate.json +31 -0
- package/examples/templates/update_phone_update_line/updatePhoneUpdateLine.js +106 -0
- package/index.js +54 -23
- package/package.json +3 -2
- package/test/tests.js +36 -30
- package/test/wsdl.js +38 -0
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Cisco AXL SOAP Library
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
A Javascript library to pull AXL data Cisco CUCM via SOAP. The goal of this project is to make it easier for people to use AXL and to include all functionality of AXL. This library utilizes [strong-soap](https://www.npmjs.com/package/strong-soap) to read Cisco's WSDL file. As a result this library can use any function in the schema for the version that you specify.
|
|
4
4
|
|
|
5
5
|
Administrative XML (AXL) information can be found at:
|
|
6
6
|
[Administrative XML (AXL) Reference](https://developer.cisco.com/docs/axl/#!axl-developer-guide).
|
|
@@ -30,10 +30,10 @@ NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
|
30
30
|
|
|
31
31
|
## Features
|
|
32
32
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
- This library uses strong-soap to parse the AXL WSDL file. As a result any AXL function for your specified version is avaliable to use!
|
|
34
|
+
- Supports the Promise API. Can chain procedures together or you could use Promise.all() to run multiple "get" operations at the same time.
|
|
35
|
+
- Returns all results in JSON rather than XML. Function has options to remove all blank or empty fields from JSON results via optional clean parameter.
|
|
36
|
+
- Support for [json-variables](https://codsen.com/os/json-variables). The executeOperation function will recognize the dataContainerIdentifierTails from json-variables and remove them from your call. This avoids any SOAP fault issues from having extra information in call. See examples folder for use case.
|
|
37
37
|
|
|
38
38
|
## Usage
|
|
39
39
|
|
|
@@ -42,37 +42,154 @@ const axlService = require("cisco-axl");
|
|
|
42
42
|
|
|
43
43
|
let service = new axlService("10.10.20.1", "administrator", "ciscopsdt");
|
|
44
44
|
|
|
45
|
-
var
|
|
46
|
-
var
|
|
45
|
+
var operation = "addRoutePartition";
|
|
46
|
+
var tags = {
|
|
47
47
|
routePartition: {
|
|
48
|
-
name:
|
|
49
|
-
description:
|
|
50
|
-
timeScheduleIdName:
|
|
51
|
-
useOriginatingDeviceTimeZone:
|
|
52
|
-
timeZone:
|
|
53
|
-
partitionUsage:
|
|
54
|
-
}
|
|
48
|
+
name: "INTERNAL-PT",
|
|
49
|
+
description: "Internal directory numbers",
|
|
50
|
+
timeScheduleIdName: "",
|
|
51
|
+
useOriginatingDeviceTimeZone: "",
|
|
52
|
+
timeZone: "",
|
|
53
|
+
partitionUsage: "",
|
|
54
|
+
},
|
|
55
55
|
};
|
|
56
56
|
|
|
57
57
|
service
|
|
58
|
-
.
|
|
59
|
-
.then((results) => {
|
|
60
|
-
console.log(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
})
|
|
65
|
-
.catch((error) => {
|
|
58
|
+
.executeOperation(operation, tags)
|
|
59
|
+
.then((results) => {
|
|
60
|
+
console.log("addRoutePartition UUID", results);
|
|
61
|
+
})
|
|
62
|
+
.catch((error) => {
|
|
66
63
|
console.log(error);
|
|
67
|
-
});
|
|
64
|
+
});
|
|
68
65
|
```
|
|
69
66
|
|
|
67
|
+
## Methods
|
|
68
|
+
|
|
69
|
+
- new axlService(options: obj)
|
|
70
|
+
- axlService.returnOperations(filter?: string)
|
|
71
|
+
- axlService.getOperationTags(operation: string)
|
|
72
|
+
- axlService.executeOperation(operation: string,tags: obj, opts?: obj)
|
|
73
|
+
|
|
74
|
+
### new axlService(options)
|
|
75
|
+
|
|
76
|
+
Service constructor for methods. Requires a JSON object consisting of hostname, username, password and version.
|
|
77
|
+
|
|
78
|
+
```node
|
|
79
|
+
let service = new axlService("10.10.20.1", "administrator", "ciscopsdt", "14.0");
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### axlService.returnOperations(filter?) ⇒ Returns promise
|
|
83
|
+
|
|
84
|
+
Method takes optional argument to filter results. No argument returns all operations. Returns results via Promise.
|
|
85
|
+
|
|
86
|
+
| Method | Argument | Type | Obligatory | Description |
|
|
87
|
+
| :--------------- | :------- | :----- | :--------- | :---------------------------------- |
|
|
88
|
+
| returnOperations | filter | string | No | Provide a string to filter results. |
|
|
89
|
+
|
|
90
|
+
### axlService.getOperationTags(operation) ⇒ Returns promise
|
|
91
|
+
|
|
92
|
+
Method requires passing an AXL operation. Returns results via Promise.
|
|
93
|
+
|
|
94
|
+
| Method | Argument | Type | Obligatory | Description |
|
|
95
|
+
| :--------------- | :-------- | :----- | :--------- | :----------------------------------------------------------------------- |
|
|
96
|
+
| getOperationTags | operation | string | Yes | Provide the name of the AXL operation you wish to retrieve the tags for. |
|
|
97
|
+
|
|
98
|
+
### axlService.executeOperation(operation,tags,opts?) ⇒ Returns promise
|
|
99
|
+
|
|
100
|
+
Method requires passing an AXL operation and JSON object of tags. Returns results via Promise.
|
|
101
|
+
|
|
102
|
+
Current options include:
|
|
103
|
+
| option | type | description |
|
|
104
|
+
| :--------------------------- | :------ | :---------------------------------------------------------------------------------- |
|
|
105
|
+
| clean | boolean | Default: **false**. Allows method to remove all tags that have no values from return data. |
|
|
106
|
+
| dataContainerIdentifierTails | string | Default: **'\_data'**. executeOperation will automatically remove any tag with the defined string. This is used with json-variables library. |
|
|
107
|
+
|
|
108
|
+
Example:
|
|
109
|
+
|
|
110
|
+
```node
|
|
111
|
+
var opts = {
|
|
112
|
+
clean: true,
|
|
113
|
+
dataContainerIdentifierTails: "_data",
|
|
114
|
+
};
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
| Method | Argument | Type | Obligatory | Description |
|
|
118
|
+
| :--------------- | :-------- | :----- | :--------- | :--------------------------------------------------------- |
|
|
119
|
+
| executeOperation | operation | string | Yes | Provide the name of the AXL operation you wish to execute. |
|
|
120
|
+
| executeOperation | tags | object | Yes | Provide a JSON object of the tags for your operation. |
|
|
121
|
+
| executeOperation | opts | object | No | Provide a JSON object of options for your operation. |
|
|
122
|
+
|
|
70
123
|
## Examples
|
|
71
124
|
|
|
72
|
-
Check
|
|
125
|
+
Check **examples** folder for different ways to use this library. Each folder should have a **README** to explain about each example.
|
|
126
|
+
|
|
127
|
+
You can also run the **tests.js** against Cisco's DevNet sandbox so see how each various method works.
|
|
73
128
|
|
|
74
129
|
```javascript
|
|
75
130
|
npm run test
|
|
76
131
|
```
|
|
77
132
|
|
|
78
|
-
Note: Test are using Cisco's DevNet sandbox information. Find more information here: [Cisco DevNet](https://devnetsandbox.cisco.com/)
|
|
133
|
+
Note: Test are using Cisco's DevNet sandbox information. Find more information here: [Cisco DevNet](https://devnetsandbox.cisco.com/).
|
|
134
|
+
|
|
135
|
+
## json-variables support
|
|
136
|
+
|
|
137
|
+
At a tactical level, json-variables program lets you take a plain object (JSON files contents) and add special markers in any value which you can then reference in a different path.
|
|
138
|
+
|
|
139
|
+
This library will recoginize json-variables **\*\_data** keys in the tags and delete before executing the operation.
|
|
140
|
+
|
|
141
|
+
Example:
|
|
142
|
+
|
|
143
|
+
```node
|
|
144
|
+
var lineTemplate = {
|
|
145
|
+
pattern: "%%_extension_%%",
|
|
146
|
+
routePartitionName: "",
|
|
147
|
+
alertingName: "%%_firstName_%% %%_lastName_%%",
|
|
148
|
+
asciiAlertingName: "%%_firstName_%% %%_lastName_%%",
|
|
149
|
+
description: "%%_firstName_%% %%_lastName_%%",
|
|
150
|
+
_data: {
|
|
151
|
+
extension: "1001",
|
|
152
|
+
firstName: "Tom",
|
|
153
|
+
lastName: "Smith",
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
const lineTags = jVar(lineTemplate);
|
|
158
|
+
|
|
159
|
+
service
|
|
160
|
+
.executeOperation("updateLine", lineTags)
|
|
161
|
+
.then((results) => {
|
|
162
|
+
console.log(results);
|
|
163
|
+
})
|
|
164
|
+
.catch((error) => {
|
|
165
|
+
console.log(error);
|
|
166
|
+
});
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Note: If you need to change the variables key you can so via options in both the json-variables and with executeOperations.
|
|
170
|
+
|
|
171
|
+
Example:
|
|
172
|
+
|
|
173
|
+
```node
|
|
174
|
+
...
|
|
175
|
+
const lineTags = jVar(lineTemplate,{ dataContainerIdentifierTails: "_variables"});
|
|
176
|
+
|
|
177
|
+
service.executeOperation("updateLine", lineTags,{ dataContainerIdentifierTails: "_variables"})
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Limitations
|
|
182
|
+
|
|
183
|
+
Currently there is an issue with strong-soap regarding returning nillable values for element tags. These values show if a particular tags is optional or not. Once resolved a method will be added to return tags nillable status (true or false).
|
|
184
|
+
|
|
185
|
+
## TODO
|
|
186
|
+
|
|
187
|
+
- Add more promised based examples, particularly a Promise.All() example.
|
|
188
|
+
- Add example for reading in CSV and performing a bulk exercise with variables.
|
|
189
|
+
- Add example for saving SQL output to CSV or uploading to cloud (Airtable or SmartSheets).
|
|
190
|
+
|
|
191
|
+
## Giving Back
|
|
192
|
+
|
|
193
|
+
If you would like to support my work and the time I put in creating the code, you can click the image below to get me a coffee. I would really appreciate it (but is not required).
|
|
194
|
+
|
|
195
|
+
[Buy Me a Coffee](https://www.buymeacoffee.com/automatebldrs)
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
const axlService = require("../../index");
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Ever wanted to change a phone model, but keep the existing one in CUCM? This script will attempt to migrate a phone to the new model.
|
|
5
|
+
It uses SQL queries to grab some of the product specific information for the new model.
|
|
6
|
+
|
|
7
|
+
We will be using axl to "getPhone" information and then make changes before calling the "addPhone" operation.
|
|
8
|
+
- First we will use the "getPhone" operation to return some values for the phone we will be using to copy.
|
|
9
|
+
- Next we will be using a number of SQL query's to validate settings needed for the new phone model
|
|
10
|
+
- Lastly we will be combining all this information so we can send them via AXL.
|
|
11
|
+
|
|
12
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the first promise to be fufilled before calling the nested one.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
// Set up new AXL service
|
|
16
|
+
let service = new axlService(
|
|
17
|
+
"10.10.20.1",
|
|
18
|
+
"administrator",
|
|
19
|
+
"ciscopsdt",
|
|
20
|
+
"14.0"
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
(async () => {
|
|
24
|
+
|
|
25
|
+
// Variable needed for script
|
|
26
|
+
|
|
27
|
+
// What model are we going to convert our phone into?
|
|
28
|
+
// You can get a list of models in CUCM with the SQL query: 'SELECT name from typemodel'
|
|
29
|
+
var convertPhoneType = "Cisco 8865"; // Another example: Cisco Dual Mode for Android
|
|
30
|
+
var phoneNameCopyingFrom = "CSFUSER001";
|
|
31
|
+
var phoneNameCopyingTo = "SEP112233445566"; // Another example: BOTWORDENJ
|
|
32
|
+
var newPhoneDescription = "Test phone added via AXL";
|
|
33
|
+
|
|
34
|
+
// We're going to use the getPhone operation to retrieve the settings from the phone we want to copy. First we'll get the JSON arguments needed for our AXL call.
|
|
35
|
+
var operation = "getPhone";
|
|
36
|
+
var tags = await service.getOperationTags(operation);
|
|
37
|
+
// Print out our operation tags
|
|
38
|
+
console.log(tags);
|
|
39
|
+
// Update the name of phone that we want to copy
|
|
40
|
+
tags.name = phoneNameCopyingFrom;
|
|
41
|
+
|
|
42
|
+
// Execute the AXL call. We set the true flag to clean the output (removes any blank or undefined settings)
|
|
43
|
+
var returnPhoneTags = await service.executeOperation(operation, tags, true);
|
|
44
|
+
|
|
45
|
+
// Now we're going to add a new phone based on the settings. We will updating a few new settings based on the model we will be converting to.
|
|
46
|
+
operation = "addPhone";
|
|
47
|
+
returnPhoneTags.phone.name = phoneNameCopyingTo; // New phone name that we will be using
|
|
48
|
+
returnPhoneTags.phone.description = newPhoneDescription; // Add a description
|
|
49
|
+
returnPhoneTags.phone.product = convertPhoneType; // Model product and type
|
|
50
|
+
returnPhoneTags.phone.model = convertPhoneType;
|
|
51
|
+
|
|
52
|
+
// Next we'll be pulling some of the supported tags for our new phone type.
|
|
53
|
+
// This may not be all the settings you have to change when converting to a new model, but are the most common that I've run into.
|
|
54
|
+
|
|
55
|
+
// Phone button templates are device and protocol specific.
|
|
56
|
+
// Let's get a list of the templates configured on our cluster that we can use.
|
|
57
|
+
|
|
58
|
+
var phoneTemplateSQL = `SELECT model.name AS device, pt.name AS template, p.buttonnum, tf.name AS feature, dp.name AS protocol
|
|
59
|
+
FROM phonetemplate AS pt, phonebutton AS p, typemodel AS model, typefeature AS tf, typedeviceprotocol AS dp
|
|
60
|
+
WHERE pt.pkid = p.fkphonetemplate AND pt.tkmodel = model.enum AND pt.tkdeviceprotocol = dp.enum AND p.tkfeature = tf.enum
|
|
61
|
+
AND model.name='${convertPhoneType}'`;
|
|
62
|
+
|
|
63
|
+
operation = "executeSQLQuery";
|
|
64
|
+
tags = await service.getOperationTags(operation);
|
|
65
|
+
tags.sql = phoneTemplateSQL;
|
|
66
|
+
var phoneTemplate = await service.executeOperation(operation, tags);
|
|
67
|
+
|
|
68
|
+
// This is for SIP phones.
|
|
69
|
+
// Let's get a list of Security Profiles configured on our cluster that we can use.
|
|
70
|
+
|
|
71
|
+
var securityProfileNameSQL = `SELECT model.name AS device, sp.name AS PROFILE
|
|
72
|
+
FROM securityprofile sp
|
|
73
|
+
LEFT OUTER JOIN typemodel AS model ON sp.tkmodel = model.enum
|
|
74
|
+
WHERE (sp.tksecuritypolicy = 4 OR sp.tksecuritypolicy = 99) AND model.name = '${convertPhoneType}'
|
|
75
|
+
ORDER BY model.name`;
|
|
76
|
+
|
|
77
|
+
operation = "executeSQLQuery";
|
|
78
|
+
tags = await service.getOperationTags(operation);
|
|
79
|
+
tags.sql = securityProfileNameSQL;
|
|
80
|
+
var securityProfileName = await service.executeOperation(operation, tags);
|
|
81
|
+
|
|
82
|
+
// DND Option can only be set to non-Zero on devices that support the DND feature (in
|
|
83
|
+
// ProductSupportsFeature table). For those devices that support the feature, only the Ringer Off (0) is
|
|
84
|
+
// valid, unless a parameter is present in the PSF record. If a parameter value of 1 exists in PSF table, only
|
|
85
|
+
// Call Reject is valid. If the param value is (2), all options including Use Common Profile (2) are valid.
|
|
86
|
+
// Dual mode and remote destination profile only support the Call Reject option.
|
|
87
|
+
|
|
88
|
+
var dndOptionSQL = `select model.name,dp.name as protocol,p.param from typemodel as model,
|
|
89
|
+
typedeviceprotocol as dp,ProductSupportsFeature as p where p.tkmodel=model.enum and p.
|
|
90
|
+
tkSupportsFeature=(select enum from typesupportsfeature where name='Do Not Disturb')
|
|
91
|
+
and p.tkdeviceprotocol=dp.enum and model.tkclass=(select enum from typeclass where name=
|
|
92
|
+
'Phone')and model.name='${convertPhoneType}' order by model.name`;
|
|
93
|
+
|
|
94
|
+
operation = "executeSQLQuery";
|
|
95
|
+
tags = await service.getOperationTags(operation);
|
|
96
|
+
tags.sql = dndOptionSQL;
|
|
97
|
+
var dndOption = await service.executeOperation(operation, tags);
|
|
98
|
+
|
|
99
|
+
// Maximum Number of Calls and Busy Trigger (Less than or equal to Max. Calls) values depend on model type.
|
|
100
|
+
// We'll use an SQL query to figure out what values we need for our model type.
|
|
101
|
+
// 200:4:2 (Max calls per device:default max calls per line:default busy trigger).
|
|
102
|
+
|
|
103
|
+
var maxBusySQL = `SELECT NAME,param
|
|
104
|
+
FROM typemodel AS model, productsupportsfeature AS p
|
|
105
|
+
WHERE p.tkmodel = model.enum
|
|
106
|
+
AND p.tksupportsfeature = (SELECT enum FROM typesupportsfeature WHERE NAME = 'Multiple Call Display')
|
|
107
|
+
AND model.tkclass = (SELECT enum FROM typeclass WHERE NAME = 'Phone') AND name='${convertPhoneType}'`;
|
|
108
|
+
|
|
109
|
+
operation = "executeSQLQuery";
|
|
110
|
+
tags = await service.getOperationTags(operation);
|
|
111
|
+
tags.sql = maxBusySQL;
|
|
112
|
+
var maxBusy = await service.executeOperation(operation, tags);
|
|
113
|
+
|
|
114
|
+
// Let's update our JSON with the necessary values from above. Note some will return multiple values. You may need to loop thru and find the one you want.
|
|
115
|
+
|
|
116
|
+
returnPhoneTags.phone.phoneTemplateName.value = phoneTemplate.row[0].template;
|
|
117
|
+
returnPhoneTags.phone.securityProfileName.value = securityProfileName.row[0].profile;
|
|
118
|
+
|
|
119
|
+
// Does this phone support DND? If so does it have a param value of 1
|
|
120
|
+
if(dndOption){
|
|
121
|
+
if(dndOption.row[0].param === '1'){
|
|
122
|
+
returnPhoneTags.phone.dndOption = "Call Reject";
|
|
123
|
+
}else{
|
|
124
|
+
returnPhoneTags.phone.dndOption = "Ringer Off";
|
|
125
|
+
}
|
|
126
|
+
}else{
|
|
127
|
+
delete returnPhoneTags.phone.dndOption;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Let's split up our values we got back from CUCM. We'll be setting each line to the default for Max Number of Calls and minus one for our Busy Trigger.
|
|
131
|
+
var maxBusyArr = maxBusy.row[0].param.split(':');
|
|
132
|
+
|
|
133
|
+
// Loop thru all lines configured and update as needed.
|
|
134
|
+
|
|
135
|
+
returnPhoneTags.phone.lines.line.map((object) => {
|
|
136
|
+
object.maxNumCalls = maxBusyArr[2];
|
|
137
|
+
object.busyTrigger = maxBusyArr[2] - 1;
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
// Confidential Access Mode is returned from CUCM via AXL as undefined if not set on the phone we are copying.
|
|
141
|
+
// We either need to set it to '' or delete it complete. Since we're not using this feature, let's delete it from our JSON.
|
|
142
|
+
|
|
143
|
+
delete returnPhoneTags.phone.confidentialAccess;
|
|
144
|
+
|
|
145
|
+
// Print out updated JSON before we execute addPhone on CUCM via AXL
|
|
146
|
+
|
|
147
|
+
console.log(returnPhoneTags);
|
|
148
|
+
|
|
149
|
+
// Let's add our new phone. If successful we should get back a UUID of our new phone.
|
|
150
|
+
operation = "addPhone";
|
|
151
|
+
await service
|
|
152
|
+
.executeOperation(operation, returnPhoneTags)
|
|
153
|
+
.then((results) => {
|
|
154
|
+
console.log("New UUID",results);
|
|
155
|
+
})
|
|
156
|
+
.catch((error) => {
|
|
157
|
+
console.log(error);
|
|
158
|
+
});
|
|
159
|
+
})();
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const axlService = require("../../index");
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Example of how to copy a phone. This is similar to the Super Copy function in CUCM. Just an example of how to do it via AXL.
|
|
5
|
+
|
|
6
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the first promise to be fufilled before calling the nested one.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
// Set up new AXL service
|
|
10
|
+
let service = new axlService(
|
|
11
|
+
"10.10.20.1",
|
|
12
|
+
"administrator",
|
|
13
|
+
"ciscopsdt",
|
|
14
|
+
"14.0"
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
(async () => {
|
|
18
|
+
var operation = "getPhone";
|
|
19
|
+
var tags = await service.getOperationTags(operation);
|
|
20
|
+
tags.name = "CSFUSER001";
|
|
21
|
+
|
|
22
|
+
var returnPhoneTags = await service.executeOperation(operation, tags, true);
|
|
23
|
+
|
|
24
|
+
operation = "addPhone";
|
|
25
|
+
returnPhoneTags.phone.name = "CSFWORDENJ";
|
|
26
|
+
returnPhoneTags.phone.description = "Test phone added via AXL";
|
|
27
|
+
// Confidential Access Mode is returned from CUCM via AXL as undefined if not set on the phone we are copying.
|
|
28
|
+
// We either need to set it to '' or delete it complete. Since we're not using this feature, let's delete it from our JSON.
|
|
29
|
+
delete returnPhoneTags.phone.confidentialAccess;
|
|
30
|
+
|
|
31
|
+
await service
|
|
32
|
+
.executeOperation(operation, returnPhoneTags)
|
|
33
|
+
.then((results) => {
|
|
34
|
+
console.log(results);
|
|
35
|
+
})
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
console.log(error);
|
|
38
|
+
});
|
|
39
|
+
})();
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
const axlService = require("../../index");
|
|
2
|
+
|
|
3
|
+
/*
|
|
4
|
+
Example of how to copy a SIP Trunk. Cisco doesn't have a Super Copy option for this, but we can do it via AXL.
|
|
5
|
+
The new SIP trunk will need to have a new IP address for the destination address.
|
|
6
|
+
|
|
7
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the first promise to be fufilled before calling the nested one.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
// Set up new AXL service
|
|
11
|
+
let service = new axlService(
|
|
12
|
+
"10.10.20.1",
|
|
13
|
+
"administrator",
|
|
14
|
+
"ciscopsdt",
|
|
15
|
+
"14.0"
|
|
16
|
+
);
|
|
17
|
+
|
|
18
|
+
(async () => {
|
|
19
|
+
// First let's get the tags needed for finding a SIP Trunk
|
|
20
|
+
var operation = "getSipTrunk";
|
|
21
|
+
var tags = await service.getOperationTags(operation);
|
|
22
|
+
|
|
23
|
+
// Let's update the returned JSON with the name of the trunk we are trying to copy
|
|
24
|
+
tags.name = "SIPTrunktoCUP";
|
|
25
|
+
|
|
26
|
+
// Make a call to AXL to get the information for the trunk we are copying.
|
|
27
|
+
// Note: we will be sending the clean flag to executeOperation. This will remove any keys in our return json that is empty, undefined or null.
|
|
28
|
+
var options = {
|
|
29
|
+
clean: true
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
var returnTrunk = await service.executeOperation(operation, tags, options);
|
|
33
|
+
|
|
34
|
+
// Update the JSON with our new values
|
|
35
|
+
operation = "addSipTrunk";
|
|
36
|
+
returnTrunk.sipTrunk.name = "SIPTrunktoCUP2";
|
|
37
|
+
returnTrunk.sipTrunk.description = "NEW SIP Trunk";
|
|
38
|
+
returnTrunk.sipTrunk.destinations.destination[0].addressIpv4 = '10.10.20.18';
|
|
39
|
+
returnTrunk.sipTrunk.destinations.destination[0].port = '5060';
|
|
40
|
+
|
|
41
|
+
// Confidential Access Mode is returned from CUCM via AXL as undefined if not set on the phone we are copying.
|
|
42
|
+
// We either need to set it to '' or delete it complete. Since we're not using this feature, let's delete it from our JSON.
|
|
43
|
+
delete returnTrunk.sipTrunk.confidentialAccess;
|
|
44
|
+
|
|
45
|
+
await service
|
|
46
|
+
.executeOperation(operation, returnTrunk)
|
|
47
|
+
.then((results) => {
|
|
48
|
+
console.log("addSipTrunk UUID", results);
|
|
49
|
+
})
|
|
50
|
+
.catch((error) => {
|
|
51
|
+
console.log(error);
|
|
52
|
+
});
|
|
53
|
+
})();
|
|
54
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
const axlService = require("../index");
|
|
2
|
+
|
|
3
|
+
// Set up new AXL service
|
|
4
|
+
let service = new axlService(
|
|
5
|
+
"10.10.20.1",
|
|
6
|
+
"administrator",
|
|
7
|
+
"ciscopsdt",
|
|
8
|
+
"14.0"
|
|
9
|
+
);
|
|
10
|
+
|
|
11
|
+
(async () => {
|
|
12
|
+
var operation = "addSipTrunk";
|
|
13
|
+
var tags = await service.getOperationTags(operation);
|
|
14
|
+
console.log(tags);
|
|
15
|
+
})();
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# SQL Queries
|
|
2
|
+
|
|
3
|
+
Use **cisco-axl** to save SQL queries and execute whenever needed. sqlQuery.js will read in an ".sql" file and run the query for you. Just export the results to a csv or use in another operation.
|
|
4
|
+
|
|
5
|
+
The **fs.readFileSync** package will allow a more readable format to be read in and still able executed with the **executeSQLQuery** operation. This makes it easy to store useful queries and script them to run.
|
|
6
|
+
|
|
7
|
+
I've included examples of some useful queries.
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
SELECT NAME,
|
|
2
|
+
param
|
|
3
|
+
FROM typemodel AS model,
|
|
4
|
+
productsupportsfeature AS p
|
|
5
|
+
WHERE p.tkmodel = model.enum
|
|
6
|
+
AND p.tksupportsfeature =
|
|
7
|
+
(SELECT enum
|
|
8
|
+
FROM typesupportsfeature
|
|
9
|
+
WHERE NAME = 'Multiple Call Display')
|
|
10
|
+
AND model.tkclass =
|
|
11
|
+
(SELECT enum
|
|
12
|
+
FROM typeclass
|
|
13
|
+
WHERE NAME = 'Phone')
|
|
14
|
+
AND name='Cisco Dual Mode for Android'
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const axlService = require("../../index");
|
|
2
|
+
var fs = require('fs');
|
|
3
|
+
var path = require('path');
|
|
4
|
+
|
|
5
|
+
/*
|
|
6
|
+
Example of how to send SQL queries to CUCM via AXL. We can store our queries in ".sql" files, then we can read them in before executing.
|
|
7
|
+
|
|
8
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the first promise to be fufilled before calling the nested one.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
// Set up new AXL service
|
|
12
|
+
let service = new axlService(
|
|
13
|
+
"10.10.20.1",
|
|
14
|
+
"administrator",
|
|
15
|
+
"ciscopsdt",
|
|
16
|
+
"14.0"
|
|
17
|
+
);
|
|
18
|
+
|
|
19
|
+
(async () => {
|
|
20
|
+
// First we'll get the params needed to call executeSQLQuery
|
|
21
|
+
var operation = "executeSQLQuery";
|
|
22
|
+
var tags = await service.getOperationTags(operation);
|
|
23
|
+
console.log(tags);
|
|
24
|
+
|
|
25
|
+
// Next we'll read in our SQL file and update tags with the query
|
|
26
|
+
let sql = fs.readFileSync(path.join(__dirname, 'mask.sql'), "utf8");
|
|
27
|
+
tags.sql = sql;
|
|
28
|
+
console.log(tags);
|
|
29
|
+
|
|
30
|
+
// Lastly let's execute the query on server
|
|
31
|
+
await service
|
|
32
|
+
.executeOperation(operation, tags)
|
|
33
|
+
.then((results) => {
|
|
34
|
+
console.log(results);
|
|
35
|
+
})
|
|
36
|
+
.catch((error) => {
|
|
37
|
+
console.log(error);
|
|
38
|
+
});
|
|
39
|
+
})();
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
SELECT np.dnorpattern AS NUMBER,
|
|
2
|
+
np.description AS DESCRIPTION,
|
|
3
|
+
cfd.cfavoicemailenabled AS CALL_FORWARD_ALL_VOICEMAIL_ENABLED,
|
|
4
|
+
cfd.cfadestination AS CALL_FORWARD_ALL_DESTINATION,
|
|
5
|
+
cfbvoicemailenabled AS BUSY_VOICEMAIL_ENABLED,
|
|
6
|
+
cfbdestination AS BUSY_EXTERNAL_DESTINTATION,
|
|
7
|
+
cfbintvoicemailenabled AS BUSY_INTERNAL_VOICEMAIL_ENABLED,
|
|
8
|
+
cfbintdestination AS BUSY_INTERNAL_DESTINATION,
|
|
9
|
+
cfnavoicemailenabled AS NOANSWER_VOICEMAIL_ENABLED,
|
|
10
|
+
cfnadestination AS NO_ANSWERD_ESTINATION,
|
|
11
|
+
cfnaintvoicemailenabled AS NO_ANSWER_INTERNAL_VOICEMAIL_ENABLED,
|
|
12
|
+
cfnaintdestination AS NO_ANSWER_INTERNAL_DESTINATION,
|
|
13
|
+
rpt.name AS PARTITION
|
|
14
|
+
FROM numplan np
|
|
15
|
+
INNER JOIN typepatternusage tpu
|
|
16
|
+
ON np.tkpatternusage = tpu.enum
|
|
17
|
+
LEFT OUTER JOIN callforwarddynamic AS cfd
|
|
18
|
+
ON cfd.fknumplan = np.pkid
|
|
19
|
+
LEFT OUTER JOIN devicenumplanmap dnmp
|
|
20
|
+
ON dnmp.fknumplan = np.pkid
|
|
21
|
+
LEFT OUTER JOIN routepartition rpt
|
|
22
|
+
ON rpt.pkid = np.fkroutepartition
|
|
23
|
+
WHERE tpu.NAME = 'Device'
|
|
24
|
+
AND dnmp.pkid IS NULL
|
|
25
|
+
ORDER BY dnorpattern ASC
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
# Templates
|
|
2
|
+
|
|
3
|
+
Using json-variables you can use JSON files as templates along with variables to automate AXL operations.
|
|
4
|
+
|
|
5
|
+
## Update Phone and Line
|
|
6
|
+
|
|
7
|
+
Every wanted to change an exsiting phone from one user to another user? This script will help you do that as well as updating all the display, line text labels, etc for the new user.
|
|
8
|
+
|
|
9
|
+
## Add Phone and Update Line
|
|
10
|
+
|
|
11
|
+
This script will add a new phone and line. Once finished it will go back and update the line. AXL doesn't allow setting alertingName, asciiAlertingName and description while adding a line via the addPhone operation. This would be similar to using BAT to add phones.
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const { jVar } = require("json-variables");
|
|
2
|
+
const axlService = require("../../../index");
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
This script using json-variables (https://codsen.com/os/json-variables) to add a new phone from a template.
|
|
6
|
+
The AXL "addPhone" operation will either add an existing line or add a new one.
|
|
7
|
+
We will be using the "updateLine" to follow behind and update a few of the fields that "addPhone" does not include.
|
|
8
|
+
|
|
9
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the "addPhone" promise to be fufilled before calling "updateLine".
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Set up new AXL service (DevNet sandbox credentials: https://devnetsandbox.cisco.com/)
|
|
13
|
+
let service = new axlService(
|
|
14
|
+
"10.10.20.1",
|
|
15
|
+
"administrator",
|
|
16
|
+
"ciscopsdt",
|
|
17
|
+
"14.0"
|
|
18
|
+
);
|
|
19
|
+
|
|
20
|
+
// Read in the JSON templates
|
|
21
|
+
var phoneTemplate = require("./phoneTemplate.json");
|
|
22
|
+
var lineTemplate = require("./lineTemplate.json");
|
|
23
|
+
|
|
24
|
+
(async () => {
|
|
25
|
+
// Use json-variables to update our values from the template values
|
|
26
|
+
const phoneArg = jVar(phoneTemplate);
|
|
27
|
+
const lineArg = jVar(lineTemplate);
|
|
28
|
+
|
|
29
|
+
// Call the first operation: "addPhone" with the jVar updated json
|
|
30
|
+
service
|
|
31
|
+
.executeOperation("addPhone", phoneArg)
|
|
32
|
+
.then((results) => {
|
|
33
|
+
// Print out the UUID for the successful "addPhone" call
|
|
34
|
+
console.log("addPhone UUID", results);
|
|
35
|
+
// Call the second operation: "update" with the jVar updated json
|
|
36
|
+
service
|
|
37
|
+
.executeOperation("updateLine", lineArg)
|
|
38
|
+
.then((results) => {
|
|
39
|
+
// Print out the UUID for the successful "updateLine" call
|
|
40
|
+
console.log("updateLine UUID", results);
|
|
41
|
+
})
|
|
42
|
+
.catch((error) => {
|
|
43
|
+
console.log(error);
|
|
44
|
+
});
|
|
45
|
+
})
|
|
46
|
+
.catch((error) => {
|
|
47
|
+
console.log(error);
|
|
48
|
+
});
|
|
49
|
+
})();
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pattern": "%%_extension_%%",
|
|
3
|
+
"routePartitionName": "",
|
|
4
|
+
"alertingName": "%%_firstName_%% %%_lastName_%%",
|
|
5
|
+
"asciiAlertingName": "%%_firstName_%% %%_lastName_%%",
|
|
6
|
+
"description": "%%_firstName_%% %%_lastName_%%",
|
|
7
|
+
"_data": {
|
|
8
|
+
"extension": "2001",
|
|
9
|
+
"firstName": "Tom",
|
|
10
|
+
"lastName": "Smith"
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
{
|
|
2
|
+
"phone":{
|
|
3
|
+
"name":"%%_deviceName_%%",
|
|
4
|
+
"description":"%%_lastName_%%, %%_firstName_%% Extension %%_extension_%%",
|
|
5
|
+
"product":"Cisco Unified Client Services Framework",
|
|
6
|
+
"model":"Cisco Unified Client Services Framework",
|
|
7
|
+
"class":"Phone",
|
|
8
|
+
"protocol":"SIP",
|
|
9
|
+
"protocolSide":"User",
|
|
10
|
+
"devicePoolName":{
|
|
11
|
+
"value":"Default"
|
|
12
|
+
},
|
|
13
|
+
"commonPhoneConfigName":{
|
|
14
|
+
"value":"Standard Common Phone Profile"
|
|
15
|
+
},
|
|
16
|
+
"networkLocation":"Use System Default",
|
|
17
|
+
"locationName":{
|
|
18
|
+
"value":"Hub_None"
|
|
19
|
+
},
|
|
20
|
+
"loadInformation":{
|
|
21
|
+
"attributes":{
|
|
22
|
+
"special":"false"
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
"versionStamp":"",
|
|
26
|
+
"traceFlag":"false",
|
|
27
|
+
"mlppIndicationStatus":"Off",
|
|
28
|
+
"preemption":"Disabled",
|
|
29
|
+
"useTrustedRelayPoint":"Default",
|
|
30
|
+
"retryVideoCallAsAudio":"true",
|
|
31
|
+
"securityProfileName":{
|
|
32
|
+
"value":"Cisco Unified Client Services Framework - UDP"
|
|
33
|
+
},
|
|
34
|
+
"sipProfileName":{
|
|
35
|
+
"value":"Standard SIP Profile"
|
|
36
|
+
},
|
|
37
|
+
"useDevicePoolCgpnTransformCss":"true",
|
|
38
|
+
"sendGeoLocation":"false",
|
|
39
|
+
"lines":{
|
|
40
|
+
"line":[
|
|
41
|
+
{
|
|
42
|
+
"index":"1",
|
|
43
|
+
"label":"%%_lastName_%% - %%_extension_%%",
|
|
44
|
+
"display":"%%_lastName_%%, %%_firstName_%%",
|
|
45
|
+
"dirn":{
|
|
46
|
+
"pattern":"%%_extension_%%"
|
|
47
|
+
},
|
|
48
|
+
"ringSetting":"Ring",
|
|
49
|
+
"consecutiveRingSetting":"Use System Default",
|
|
50
|
+
"mwlPolicy":"Use System Policy",
|
|
51
|
+
"displayAscii":"%%_lastName_%%, %%_firstName_%%",
|
|
52
|
+
"maxNumCalls":"3",
|
|
53
|
+
"busyTrigger":"2",
|
|
54
|
+
"callInfoDisplay":{
|
|
55
|
+
"callerName":"true",
|
|
56
|
+
"callerNumber":"false",
|
|
57
|
+
"redirectedNumber":"false",
|
|
58
|
+
"dialedNumber":"true"
|
|
59
|
+
},
|
|
60
|
+
"recordingFlag":"Call Recording Disabled",
|
|
61
|
+
"audibleMwi":"Default",
|
|
62
|
+
"partitionUsage":"General",
|
|
63
|
+
"associatedEndusers":{
|
|
64
|
+
"enduser":[
|
|
65
|
+
{
|
|
66
|
+
"userId":"%%_userid_%%"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
},
|
|
70
|
+
"missedCallLogging":"true",
|
|
71
|
+
"recordingMediaSource":"Gateway Preferred"
|
|
72
|
+
}
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"numberOfButtons":"1",
|
|
76
|
+
"phoneTemplateName":{
|
|
77
|
+
"value":"Standard Client Services Framework"
|
|
78
|
+
},
|
|
79
|
+
"ringSettingIdleBlfAudibleAlert":"Default",
|
|
80
|
+
"ringSettingBusyBlfAudibleAlert":"Default",
|
|
81
|
+
"enableExtensionMobility":"false",
|
|
82
|
+
"currentProfileName":{
|
|
83
|
+
|
|
84
|
+
},
|
|
85
|
+
"currentConfig":{
|
|
86
|
+
"phoneTemplateName":{
|
|
87
|
+
"value":"Standard Client Services Framework"
|
|
88
|
+
},
|
|
89
|
+
"mlppIndicationStatus":"Off",
|
|
90
|
+
"preemption":"Disabled",
|
|
91
|
+
"ignorePresentationIndicators":"false",
|
|
92
|
+
"singleButtonBarge":"Default",
|
|
93
|
+
"joinAcrossLines":"Off",
|
|
94
|
+
"callInfoPrivacyStatus":"Default",
|
|
95
|
+
"dndRingSetting":"Disable",
|
|
96
|
+
"dndOption":"Ringer Off",
|
|
97
|
+
"alwaysUsePrimeLine":"Default",
|
|
98
|
+
"alwaysUsePrimeLineForVoiceMessage":"Default",
|
|
99
|
+
"emccCallingSearchSpaceName":{
|
|
100
|
+
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
"singleButtonBarge":"Default",
|
|
104
|
+
"joinAcrossLines":"Off",
|
|
105
|
+
"builtInBridgeStatus":"Default",
|
|
106
|
+
"callInfoPrivacyStatus":"Default",
|
|
107
|
+
"hlogStatus":"On",
|
|
108
|
+
"ignorePresentationIndicators":"false",
|
|
109
|
+
"packetCaptureMode":"None",
|
|
110
|
+
"packetCaptureDuration":"0",
|
|
111
|
+
"allowCtiControlFlag":"true",
|
|
112
|
+
"presenceGroupName":{
|
|
113
|
+
"value":"Standard Presence group"
|
|
114
|
+
},
|
|
115
|
+
"unattendedPort":"false",
|
|
116
|
+
"requireDtmfReception":"false",
|
|
117
|
+
"rfc2833Disabled":"false",
|
|
118
|
+
"certificateOperation":"No Pending Operation",
|
|
119
|
+
"certificateStatus":"None",
|
|
120
|
+
"deviceMobilityMode":"Default",
|
|
121
|
+
"remoteDevice":"false",
|
|
122
|
+
"dndOption":"Ringer Off",
|
|
123
|
+
"dndRingSetting":"Disable",
|
|
124
|
+
"dndStatus":"false",
|
|
125
|
+
"isActive":"true",
|
|
126
|
+
"isDualMode":"true",
|
|
127
|
+
"phoneSuite":"Default",
|
|
128
|
+
"phoneServiceDisplay":"Default",
|
|
129
|
+
"isProtected":"false",
|
|
130
|
+
"mtpRequired":"false",
|
|
131
|
+
"mtpPreferedCodec":"711ulaw",
|
|
132
|
+
"outboundCallRollover":"No Rollover",
|
|
133
|
+
"hotlineDevice":"false",
|
|
134
|
+
"alwaysUsePrimeLine":"Default",
|
|
135
|
+
"alwaysUsePrimeLineForVoiceMessage":"Default",
|
|
136
|
+
"deviceTrustMode":"Not Trusted",
|
|
137
|
+
"AllowPresentationSharingUsingBfcp":"false",
|
|
138
|
+
"allowiXApplicableMedia":"false",
|
|
139
|
+
"useDevicePoolCgpnIngressDN":"true",
|
|
140
|
+
"enableCallRoutingToRdWhenNoneIsActive":"f",
|
|
141
|
+
"enableActivationID":"false",
|
|
142
|
+
"allowMraMode":"false"
|
|
143
|
+
},
|
|
144
|
+
"phone_data":{
|
|
145
|
+
"firstName":"Tom",
|
|
146
|
+
"lastName":"Smith",
|
|
147
|
+
"extension":"2001",
|
|
148
|
+
"userid":"user01",
|
|
149
|
+
"deviceName": "CSFSMITHT"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
{
|
|
2
|
+
"pattern": "%%_extension_%%",
|
|
3
|
+
"routePartitionName": "",
|
|
4
|
+
"alertingName": "%%_firstName_%% %%_lastName_%%",
|
|
5
|
+
"asciiAlertingName": "%%_firstName_%% %%_lastName_%%",
|
|
6
|
+
"description": "%%_firstName_%% %%_lastName_%%",
|
|
7
|
+
"_data": {
|
|
8
|
+
"extension": "",
|
|
9
|
+
"firstName": "",
|
|
10
|
+
"lastName": ""
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name":"%%_deviceName_%%",
|
|
3
|
+
"description":"%%_lastName_%%, %%_firstName_%% Extension %%_extension_%%",
|
|
4
|
+
"ownerUserName": "%%_userid_%%",
|
|
5
|
+
"lines":{
|
|
6
|
+
"line":[
|
|
7
|
+
{
|
|
8
|
+
"index":"1",
|
|
9
|
+
"dirn":{
|
|
10
|
+
"pattern":"%%_extension_%%"
|
|
11
|
+
},
|
|
12
|
+
"label":"%%_lastName_%% - %%_extension_%%",
|
|
13
|
+
"display":"%%_lastName_%%, %%_firstName_%%",
|
|
14
|
+
"displayAscii":"%%_lastName_%%, %%_firstName_%%",
|
|
15
|
+
"associatedEndusers":{
|
|
16
|
+
"enduser":[
|
|
17
|
+
{
|
|
18
|
+
"userId":"%%_userid_%%"
|
|
19
|
+
}
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}]
|
|
23
|
+
},
|
|
24
|
+
"_data": {
|
|
25
|
+
"deviceName":"",
|
|
26
|
+
"extension": "",
|
|
27
|
+
"firstName": "",
|
|
28
|
+
"lastName": "",
|
|
29
|
+
"userid":""
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
const { jVar } = require("json-variables");
|
|
2
|
+
const axlService = require("../../../index");
|
|
3
|
+
|
|
4
|
+
/*
|
|
5
|
+
Every wanted to change an exsiting phone from one user to another user? This script will help you do that as well as updating all the display,
|
|
6
|
+
line text labels, etc for the new user.
|
|
7
|
+
|
|
8
|
+
This script using json-variables (https://codsen.com/os/json-variables) to update a phone from a template.
|
|
9
|
+
|
|
10
|
+
We will be updating a phone from an existing user to a new user. This is a common MACD change for help desks.
|
|
11
|
+
- First we will use the "getUser" operation to return some values for the user we will be using to replace the existing user.
|
|
12
|
+
- Next we will take those values and update some of templates values.
|
|
13
|
+
- Next we will use jVar to merge those values for us so we can send them via AXL.
|
|
14
|
+
|
|
15
|
+
Note: axlService is Promised based, so we using a nested promise. We wait for the first promise to be fufilled before calling the nested one.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
// Set up new AXL service (DevNet sandbox credentials: https://devnetsandbox.cisco.com/)
|
|
19
|
+
let service = new axlService(
|
|
20
|
+
"10.10.20.1",
|
|
21
|
+
"administrator",
|
|
22
|
+
"ciscopsdt",
|
|
23
|
+
"14.0"
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
// Read in the JSON templates
|
|
27
|
+
var phoneTemplate = require("./phoneTemplate.json");
|
|
28
|
+
var lineTemplate = require("./lineTemplate.json");
|
|
29
|
+
|
|
30
|
+
// Set up the tags for our "getUser" call. We are interested in getting the first and last name to use in our template later.
|
|
31
|
+
|
|
32
|
+
var deviceToUpdate = {
|
|
33
|
+
deviceName: "CSFUSER005",
|
|
34
|
+
extension: "1005"
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// User to update phone value to
|
|
38
|
+
|
|
39
|
+
var getUserJSON = {
|
|
40
|
+
userid: "user03",
|
|
41
|
+
returnedTags: {
|
|
42
|
+
firstName: "",
|
|
43
|
+
lastName: "",
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
(async () => {
|
|
48
|
+
// Call getUser operation and store in the userInfo variable
|
|
49
|
+
var userInfo = await service
|
|
50
|
+
.executeOperation("getUser", getUserJSON)
|
|
51
|
+
.catch((error) => {
|
|
52
|
+
console.log(error);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Let's update our variable data with the information that we got back from our AXL call
|
|
56
|
+
phoneTemplate._data.firstName = userInfo.user.firstName;
|
|
57
|
+
phoneTemplate._data.lastName = userInfo.user.lastName;
|
|
58
|
+
phoneTemplate._data.userid = getUserJSON.userid;
|
|
59
|
+
phoneTemplate._data.deviceName = deviceToUpdate.deviceName;
|
|
60
|
+
phoneTemplate._data.extension = deviceToUpdate.extension;
|
|
61
|
+
lineTemplate._data.firstName = userInfo.user.firstName;
|
|
62
|
+
lineTemplate._data.lastName = userInfo.user.lastName;
|
|
63
|
+
lineTemplate._data.extension = deviceToUpdate.extension;
|
|
64
|
+
|
|
65
|
+
// Use json-variables to update our values from the template values
|
|
66
|
+
const phoneTags = jVar(phoneTemplate);
|
|
67
|
+
const lineTags = jVar(lineTemplate);
|
|
68
|
+
|
|
69
|
+
// Call the first operation: "updatePhone" with the jVar updated json
|
|
70
|
+
service
|
|
71
|
+
.executeOperation("updatePhone", phoneTags)
|
|
72
|
+
.then((results) => {
|
|
73
|
+
// Print out the UUID for the successful "updatePhone" call
|
|
74
|
+
console.log("updatePhone UUID", results);
|
|
75
|
+
// Call the second operation: "updateLine" with the jVar updated json
|
|
76
|
+
service
|
|
77
|
+
.executeOperation("updateLine", lineTags)
|
|
78
|
+
.then((results) => {
|
|
79
|
+
// Print out the UUID for the successful "updateLine" call
|
|
80
|
+
console.log("updateLine UUID", results);
|
|
81
|
+
// Lastly let's update our user with the controlled device and set the primary ex
|
|
82
|
+
var updateUserJSON = {
|
|
83
|
+
userid: getUserJSON.userid,
|
|
84
|
+
associatedDevices: {
|
|
85
|
+
device: [deviceToUpdate.deviceName],
|
|
86
|
+
},
|
|
87
|
+
primaryExtension: { pattern: deviceToUpdate.extension, routePartitionName: '' }
|
|
88
|
+
};
|
|
89
|
+
service
|
|
90
|
+
.executeOperation("updateUser", updateUserJSON)
|
|
91
|
+
.then((results) => {
|
|
92
|
+
// Print out the UUID for the successful "updateUser" call
|
|
93
|
+
console.log("updateUser UUID", results);
|
|
94
|
+
})
|
|
95
|
+
.catch((error) => {
|
|
96
|
+
console.log(error);
|
|
97
|
+
});
|
|
98
|
+
})
|
|
99
|
+
.catch((error) => {
|
|
100
|
+
console.log(error);
|
|
101
|
+
});
|
|
102
|
+
})
|
|
103
|
+
.catch((error) => {
|
|
104
|
+
console.log(error);
|
|
105
|
+
});
|
|
106
|
+
})();
|
package/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
const soap = require("strong-soap").soap;
|
|
2
|
+
const WSDL = soap.WSDL;
|
|
3
|
+
const wsdlOptions = {
|
|
4
|
+
attributesKey: "attributes",
|
|
5
|
+
valueKey: "value",
|
|
6
|
+
};
|
|
5
7
|
|
|
6
8
|
/**
|
|
7
9
|
* Cisco axlService Service
|
|
@@ -20,10 +22,10 @@ class axlService {
|
|
|
20
22
|
version: version,
|
|
21
23
|
};
|
|
22
24
|
}
|
|
23
|
-
|
|
25
|
+
returnOperations(filter) {
|
|
24
26
|
var options = this._OPTIONS;
|
|
25
27
|
return new Promise((resolve, reject) => {
|
|
26
|
-
soap.createClient(options.url,
|
|
28
|
+
soap.createClient(options.url, wsdlOptions, function (err, client) {
|
|
27
29
|
client.setSecurity(
|
|
28
30
|
new soap.BasicAuthSecurity(options.username, options.password)
|
|
29
31
|
);
|
|
@@ -59,7 +61,7 @@ class axlService {
|
|
|
59
61
|
});
|
|
60
62
|
});
|
|
61
63
|
}
|
|
62
|
-
|
|
64
|
+
getOperationTags(operation) {
|
|
63
65
|
var options = this._OPTIONS;
|
|
64
66
|
return new Promise((resolve, reject) => {
|
|
65
67
|
WSDL.open(
|
|
@@ -69,10 +71,10 @@ class axlService {
|
|
|
69
71
|
if (err) {
|
|
70
72
|
reject(err);
|
|
71
73
|
}
|
|
72
|
-
var
|
|
73
|
-
wsdl.definitions.bindings.AXLAPIBinding.operations[
|
|
74
|
-
var operName =
|
|
75
|
-
var operationDesc =
|
|
74
|
+
var operationDef =
|
|
75
|
+
wsdl.definitions.bindings.AXLAPIBinding.operations[operation];
|
|
76
|
+
var operName = operationDef.$name;
|
|
77
|
+
var operationDesc = operationDef.describe(wsdl);
|
|
76
78
|
var envelopeBody = {};
|
|
77
79
|
operationDesc.input.body.elements.map((object) => {
|
|
78
80
|
var operMatch = new RegExp(object.qname.name, "i");
|
|
@@ -95,13 +97,17 @@ class axlService {
|
|
|
95
97
|
);
|
|
96
98
|
});
|
|
97
99
|
}
|
|
98
|
-
|
|
100
|
+
executeOperation(operation, tags, opts) {
|
|
99
101
|
var options = this._OPTIONS;
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
102
|
+
|
|
103
|
+
var clean = opts?.clean ? opts.clean : false;
|
|
104
|
+
var dataContainerIdentifierTails = opts?.dataContainerIdentifierTails ? opts.dataContainerIdentifierTails : '_data';
|
|
105
|
+
|
|
106
|
+
// Let's remove empty top level strings. Also filter out json-variables
|
|
107
|
+
Object.keys(tags).forEach((k) => (tags[k] == '' || k.includes(dataContainerIdentifierTails)) && delete tags[k]);
|
|
108
|
+
|
|
103
109
|
return new Promise((resolve, reject) => {
|
|
104
|
-
soap.createClient(options.url,
|
|
110
|
+
soap.createClient(options.url, wsdlOptions, function (err, client) {
|
|
105
111
|
client.setSecurity(
|
|
106
112
|
new soap.BasicAuthSecurity(options.username, options.password)
|
|
107
113
|
);
|
|
@@ -111,22 +117,27 @@ class axlService {
|
|
|
111
117
|
reject(err.root.Envelope.Body.Fault);
|
|
112
118
|
});
|
|
113
119
|
|
|
114
|
-
var axlFunc = client.AXLAPIService.AXLPort[
|
|
120
|
+
var axlFunc = client.AXLAPIService.AXLPort[operation];
|
|
115
121
|
|
|
116
122
|
axlFunc(
|
|
117
|
-
|
|
123
|
+
tags,
|
|
118
124
|
function (
|
|
119
125
|
err,
|
|
120
126
|
result,
|
|
121
|
-
envelope,
|
|
122
|
-
rawResponse,
|
|
123
|
-
soapHeader,
|
|
124
|
-
rawRequest
|
|
125
127
|
) {
|
|
126
128
|
if (err) {
|
|
127
129
|
reject(err);
|
|
128
130
|
}
|
|
129
|
-
|
|
131
|
+
if (result?.hasOwnProperty("return")) {
|
|
132
|
+
var output = result.return;
|
|
133
|
+
if (clean) {
|
|
134
|
+
output = cleanObj(result.return);
|
|
135
|
+
}
|
|
136
|
+
resolve(output);
|
|
137
|
+
|
|
138
|
+
} else {
|
|
139
|
+
reject('No return results');
|
|
140
|
+
}
|
|
130
141
|
}
|
|
131
142
|
);
|
|
132
143
|
});
|
|
@@ -154,4 +165,24 @@ const nestedObj = (object) => {
|
|
|
154
165
|
}
|
|
155
166
|
};
|
|
156
167
|
|
|
168
|
+
const cleanObj = (object) => {
|
|
169
|
+
Object.entries(object).forEach(([k, v]) => {
|
|
170
|
+
if (v && typeof v === "object") {
|
|
171
|
+
cleanObj(v);
|
|
172
|
+
}
|
|
173
|
+
if (
|
|
174
|
+
(v && typeof v === "object" && !Object.keys(v).length) ||
|
|
175
|
+
v === null ||
|
|
176
|
+
v === undefined
|
|
177
|
+
) {
|
|
178
|
+
if (Array.isArray(object)) {
|
|
179
|
+
object.splice(k, 1);
|
|
180
|
+
} else {
|
|
181
|
+
delete object[k];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
return object;
|
|
186
|
+
};
|
|
187
|
+
|
|
157
188
|
module.exports = axlService;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cisco-axl",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.1.1",
|
|
4
|
+
"description": "A library to make Cisco AXL a lot easier",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "NODE_OPTIONS=--experimental-vm-modules NODE_NO_WARNINGS=1 NODE_TLS_REJECT_UNAUTHORIZED=0 node ./test/tests.js"
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"strong-soap": "^3.4.0"
|
|
22
22
|
},
|
|
23
23
|
"devDependencies": {
|
|
24
|
+
"json-variables": "^10.1.0",
|
|
24
25
|
"node-emoji": "^1.11.0"
|
|
25
26
|
}
|
|
26
27
|
}
|
package/test/tests.js
CHANGED
|
@@ -20,70 +20,76 @@ var computer = emoji.get("computer");
|
|
|
20
20
|
var finished = emoji.get("raised_hand");
|
|
21
21
|
|
|
22
22
|
(async () => {
|
|
23
|
-
console.log(`${spy} Let's first get a list of all the
|
|
24
|
-
var
|
|
25
|
-
console.log(computer,
|
|
23
|
+
console.log(`${spy} Let's first get a list of all the operations.`);
|
|
24
|
+
var operationArr = await service.returnOperations();
|
|
25
|
+
console.log(computer, "Found", operationArr.length,"operations.");
|
|
26
|
+
const random = Math.floor(Math.random() * operationArr.length);
|
|
27
|
+
console.log(`${spy} Let's pick out out at random operation: '`,operationArr[random],"'");
|
|
26
28
|
console.log(
|
|
27
|
-
`${next} Now let's get a list of all the
|
|
29
|
+
`${next} Now let's get a list of all the operations that include the word 'partition'.`
|
|
28
30
|
);
|
|
29
|
-
var
|
|
30
|
-
console.log(computer,
|
|
31
|
+
var operationFilteredArr = await service.returnOperations("partition");
|
|
32
|
+
console.log(computer, operationFilteredArr);
|
|
31
33
|
|
|
32
34
|
console.log(
|
|
33
|
-
`${cat} Great. Let's add a new route partition via 'addRoutePartition'.`
|
|
35
|
+
`${cat} Great. Let's add a new route partition via 'addRoutePartition' operation.`
|
|
34
36
|
);
|
|
35
37
|
|
|
36
|
-
var
|
|
38
|
+
var operation = "addRoutePartition";
|
|
37
39
|
|
|
38
40
|
console.log(
|
|
39
|
-
`${next} We'll need to get what
|
|
41
|
+
`${next} We'll need to get what tags to pass to the SOAP client first.`
|
|
40
42
|
);
|
|
41
|
-
var
|
|
42
|
-
console.log(computer,
|
|
43
|
-
console.log(
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
43
|
+
var tags = await service.getOperationTags(operation);
|
|
44
|
+
console.log(computer, tags);
|
|
45
|
+
console.log(
|
|
46
|
+
`${sparkles} Magnificent, let's update the name and description fields.`
|
|
47
|
+
);
|
|
48
|
+
tags.routePartition.name = "INTERNAL-PT";
|
|
49
|
+
tags.routePartition.description = "Internal directory numbers";
|
|
50
|
+
console.log(computer, tags);
|
|
47
51
|
console.log(
|
|
48
52
|
`${next} Now we will attempt to add the new partition. Function should return an UUID to represent the new partition.`
|
|
49
53
|
);
|
|
50
54
|
await service
|
|
51
|
-
.
|
|
55
|
+
.executeOperation(operation, tags)
|
|
52
56
|
.then(async (results) => {
|
|
53
|
-
console.log(computer,results);
|
|
57
|
+
console.log(computer, results);
|
|
54
58
|
console.log(
|
|
55
|
-
`${cat}
|
|
59
|
+
`${cat} Awesome, let's get a list of all the partitions on our cluster now. First we'll get the tags needed for this call.`
|
|
56
60
|
);
|
|
57
61
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
console.log(computer,
|
|
62
|
+
operation = "listRoutePartition";
|
|
63
|
+
tags = await service.getOperationTags(operation);
|
|
64
|
+
console.log(computer, tags);
|
|
61
65
|
|
|
62
66
|
console.log(
|
|
63
67
|
`${spy} We want to list all partitions, so we'll be searching for a wildcard (%%) in the name and description fields.`
|
|
64
68
|
);
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.log(computer,
|
|
69
|
+
tags.searchCriteria.name = "%%";
|
|
70
|
+
tags.searchCriteria.description = "%%";
|
|
71
|
+
console.log(computer, tags);
|
|
68
72
|
console.log(
|
|
69
|
-
`${sparkles}
|
|
73
|
+
`${sparkles} Astounding, now that we've updated our tags, we'll send the AXL request via SOAP.`
|
|
70
74
|
);
|
|
71
75
|
|
|
72
76
|
await service
|
|
73
|
-
.
|
|
77
|
+
.executeOperation(operation, tags)
|
|
74
78
|
.then((results) => {
|
|
75
|
-
console.log(
|
|
79
|
+
console.log(
|
|
80
|
+
`${list} Here are a list of all the partitions on our cluster:`
|
|
81
|
+
);
|
|
76
82
|
results.routePartition.map((str) => {
|
|
77
83
|
var outString = `${check} ${str.name}`;
|
|
78
84
|
console.log(outString);
|
|
79
85
|
});
|
|
80
86
|
})
|
|
81
87
|
.catch((error) => {
|
|
82
|
-
console.log(skull,error);
|
|
88
|
+
console.log(skull, error);
|
|
83
89
|
});
|
|
84
90
|
})
|
|
85
91
|
.catch((error) => {
|
|
86
|
-
console.log(skull,"Adding a new partition failed", error);
|
|
92
|
+
console.log(skull, "Adding a new partition failed", error);
|
|
87
93
|
});
|
|
88
|
-
console.log(finished,"Test all finished. Thanks!");
|
|
94
|
+
console.log(finished, "Test all finished. Thanks!");
|
|
89
95
|
})();
|
package/test/wsdl.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
var soap = require("strong-soap").soap;
|
|
2
|
+
var WSDL = soap.WSDL;
|
|
3
|
+
|
|
4
|
+
var method = "addPhone";
|
|
5
|
+
|
|
6
|
+
const wsdlOptions = {
|
|
7
|
+
attributesKey: 'attributes',
|
|
8
|
+
valueKey: 'value',
|
|
9
|
+
xmlKey: 'xml',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
WSDL.open(`./schema/current/AXLAPI.wsdl`, wsdlOptions, function (err, wsdl) {
|
|
13
|
+
if (err) {
|
|
14
|
+
reject(err);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
var operation = wsdl.definitions.bindings.AXLAPIBinding.operations[method];
|
|
18
|
+
var schemas = wsdl.definitions;
|
|
19
|
+
var schema = wsdl.definitions.schemas['http://www.cisco.com/AXL/API/14.0'];
|
|
20
|
+
var operName = operation.$name;
|
|
21
|
+
var part = wsdl.definitions.messages.AXLError.parts;
|
|
22
|
+
var complexType = schema.complexTypes[method];
|
|
23
|
+
var operationDesc = operation.describe(wsdl);
|
|
24
|
+
var requestElements = operationDesc.input.body.elements[0].elements;
|
|
25
|
+
|
|
26
|
+
operationDesc.input.body.elements.map((object) => {
|
|
27
|
+
var operMatch = new RegExp(object.qname.name, "i");
|
|
28
|
+
if (operName.match(operMatch)) {
|
|
29
|
+
nestedObj(object);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
const nestedObj = (object) => {
|
|
35
|
+
object.elements.map((object) => {
|
|
36
|
+
console.log(object.qname.name);
|
|
37
|
+
});
|
|
38
|
+
};
|