un-cli 0.0.58 → 0.0.61

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/adminlog.mjs CHANGED
@@ -13,8 +13,8 @@ export class AdminLog {
13
13
  this.token = token;
14
14
  }
15
15
 
16
- query (codes, serviceName ="*", pageSize = 10000)
17
- {
16
+ query (codes, serviceName ="*", pageSize = 100000, startTime = null, endTime = null)
17
+ {
18
18
  const url = this.adminServerUrl + "/logs/query?f=pjson"
19
19
  const level = "DEBUG"
20
20
  const filterType="json"
@@ -24,7 +24,13 @@ export class AdminLog {
24
24
  "services": serviceName
25
25
  }
26
26
 
27
- const queryLogUrl = url + `&token=${token}&level=${level}&filterType=${filterType}&filter=${encodeURIComponent(JSON.stringify(filter))}&pageSize=${pageSize}`
27
+ let queryLogUrl = url + `&token=${token}&level=${level}&filterType=${filterType}&filter=${encodeURIComponent(JSON.stringify(filter))}&pageSize=${pageSize}`
28
+ if (startTime != null)
29
+ queryLogUrl += `&startTime=${startTime}`
30
+
31
+ if (endTime != null)
32
+ queryLogUrl += `&endTime=${endTime}`
33
+
28
34
  //logger.info(queryLogUrl);
29
35
  return fetch(queryLogUrl);
30
36
  }
package/cmd.txt CHANGED
@@ -1 +1,4 @@
1
+ subnetworks
2
+ arlogs --byrule --maxguid
3
+ arlogs --byrule --minguid
1
4
  arlogs --byrule
package/index.mjs CHANGED
@@ -7,13 +7,10 @@ import { AdminLog } from "./adminlog.mjs"
7
7
  import { logger } from "./logger.mjs"
8
8
  import fetch from "node-fetch"
9
9
  //update version
10
- let version = "0.0.58";
10
+ let version = "0.0.61";
11
11
  const GENERATE_TOKEN_TIME_MIN = 30;
12
12
 
13
13
  let rl = null;
14
-
15
-
16
- //uncli --portal https://utilitynetwork.esri.com/portal --service AllStar_oracle --user unadmin --password unadmin.108
17
14
  let portal = null;
18
15
  let un = null;
19
16
  let adminLog = null;
@@ -29,9 +26,11 @@ function parseInput(){
29
26
  "--command",
30
27
  "--gdbversion",
31
28
  "--file",
32
- "--verify"
29
+ "--verify",
30
+ "--server"
33
31
  ]
34
-
32
+
33
+ //null marked parmaters are required
35
34
  const params = {
36
35
  "portal": null,
37
36
  "service": null,
@@ -40,7 +39,8 @@ function parseInput(){
40
39
  "command": "",
41
40
  "gdbversion": "SDE.DEFAULT",
42
41
  "file": "",
43
- "verify": "true"
42
+ "verify": "true",
43
+ "server": undefined
44
44
  }
45
45
 
46
46
  for (let i = 0; i < process.argv.length ; i++){
@@ -53,9 +53,10 @@ function parseInput(){
53
53
 
54
54
  if (Object.values(params).includes(null))
55
55
  {
56
- console.log ("HELP: uncli --portal https://unportal.domain.com/portal --service servicename --user username --password password [--gdbversion* user.version --file commandfile* --verify true|false]")
56
+ console.log ("HELP: uncli --portal https://unportal.domain.com/portal --service servicename --user username --password password [--gdbversion* user.version --server https://federatedserver.domain.com/server --file commandfile* --verify true|false]")
57
57
  console.log("--file commandfile is optional and you can pass a path to a file with a list of command to execute. ")
58
58
  console.log("--gdbversion is optional and allows the UN to be opened in that version. When not specified sde.DEFAULT is used.")
59
+ console.log("--server is optional except when there are more than one federated server sites to the portal. If the portal only has one server it will be selected.")
59
60
  process.exit();
60
61
  }
61
62
 
@@ -64,7 +65,7 @@ function parseInput(){
64
65
 
65
66
  //
66
67
  async function getToken(parameters) {
67
- portal = new Portal(parameters.portal, parameters.user, parameters.password)
68
+ portal = new Portal(parameters.portal, parameters.user, parameters.password,300, parameters.server)
68
69
  logger.info("About to connect..")
69
70
  const token = await portal.connect()
70
71
  logger.info(`Token generanted successfully.`)
@@ -102,9 +103,9 @@ async function connect(parameters) {
102
103
  un = new UtilityNetwork(token, serviceUrl, parameters.gdbversion)
103
104
  //create a new admin object (user might not be admin we won't use it until the user call log )
104
105
  adminLog = new AdminLog(token, portal.serverUrl)
105
- console.log("Loading utility network...")
106
+ logger.info("Loading utility network...")
106
107
  await un.load();
107
- console.log("Connected.")
108
+ logger.info("Connected.")
108
109
  /*test here*/
109
110
 
110
111
  //if user specified file path open it read all commands and execute them
@@ -178,7 +179,7 @@ const inputs = {
178
179
  "count --system": "Lists the number of rows in system layers.",
179
180
  "connect --service": "Connects to the another service",
180
181
  "arlogs": "Lists attribute rules execution logs (requires admin)",
181
- "arlogs --byrule": "Lists attribute rules execution summary by rule (requires admin)",
182
+ "arlogs --byrule [--minguid --maxguid]": "Lists attribute rules execution summary by rule (requires admin), --maxguid and --minguid show the GUID of the feature",
182
183
  "whoami": "Lists the current login info",
183
184
  "clear": "Clears this screen",
184
185
  "quit": "Exit this program"
@@ -673,10 +674,26 @@ const inputs = {
673
674
  },
674
675
 
675
676
  "^arlogs$": async () => {
677
+ const topLogCount = 200;
678
+ const pageSize = 10000
676
679
  console.log(`Querying attribute rules logs for ${parameters.service} ...`)
677
- const result= await adminLog.query([102003], parameters.service)
678
- const jsonRes = await result.json()
679
- const arMessages = jsonRes.logMessages
680
+ console.log(`Displaying top ${topLogCount} entries only..`)
681
+
682
+ let result= await adminLog.query([102003], [parameters.service+ ".MapServer"], topLogCount)
683
+ let jsonRes = await result.json()
684
+ let allMessages = [].concat(jsonRes.logMessages)
685
+
686
+ while (jsonRes.hasMore && allMessages.filter(m => m.message.indexOf("Attribute rule execution complete:") > -1).length < topLogCount )
687
+ {
688
+ //start paging
689
+ logger.info(`Aggregating messages... total so far ${allMessages.length} debug entries but more left, pulling logs before ${new Date(jsonRes.endTime)}`)
690
+ result= await adminLog.query([102003], [parameters.service + ".MapServer"], pageSize, jsonRes.endTime)
691
+ jsonRes = await result.json()
692
+ allMessages = allMessages.concat(jsonRes.logMessages)
693
+ }
694
+
695
+
696
+ const arMessages = allMessages
680
697
  .filter(m => m.message.indexOf("Attribute rule execution complete:") > -1)
681
698
  .map (m => JSON.parse(m.message.replace("Attribute rule execution complete:", "")))
682
699
  .map( m => {
@@ -690,30 +707,41 @@ const inputs = {
690
707
  return m
691
708
  })
692
709
  .sort( (m1, m2) => m2["Elapsed Time (ms)"]- m1["Elapsed Time (ms)"])
710
+ .slice(0, topLogCount);
693
711
  console.table(arMessages)
694
- /*
695
- code:102003
696
- elapsed:''
697
- machine:'DEV0015932.ESRI.COM'
698
- message:'requestProperties = {"token":"HfauSFGoSwTMA5KLvsL-I8EORea_KEvz1GcAMNCsvTuzeJ1QuYbQm0EGI7eC2zr1lOm8857U18oZOjG0BeuEwKj7fvCk-_DuWKFvClU5p06SRLE8RjEzPB0gjMTFQnjVnTRmQzZFWXCj1VRssMECQg..","referer":null,"privilege":"ADMINISTER","privileges":["features:user:edit","features:user:fullEdit","features:user:manageVersions","portal:user:viewTracks","premium:user:geocode:stored","premium:user:geocode:temporary","premium:user:networkanalysis:closestfacility","premium:user:networkanalysis:locationallocation","premium:user:networkanalysis:optimizedrouting","premium:user:networkanalysis:origindestinationcostmatrix","premium:user:networkanalysis:routing","premium:user:networkanalysis:servicearea","premium:user:networkanalysis:vehiclerouting","traceNetwork","utilityNetwork","parcelFabric"],"securityProvider":"portal"}'
699
- methodName:'GetServerEnvironmentRequestProperties'
700
- process:'30940'
701
- requestID:'fccb7fba-cba9-4ebb-84b2-cb3645979d8e'
702
- source:'RedTrolley_Postgres.MapServer'
703
- thread:'29408'
704
- time:1634165691074
705
- type:'DEBUG'
706
- user:'unadmin'
707
- */
712
+
708
713
 
709
714
  },
710
715
 
711
716
 
712
- "^arlogs --byrule$": async () => {
713
- console.log(`Querying attribute rules logs for ${parameters.service} ...`)
714
- const result= await adminLog.query([102003], parameters.service)
715
- const jsonRes = await result.json()
716
- const arMessages = jsonRes.logMessages
717
+ "^arlogs --byrule": async input => {
718
+ //--minguid to show min guid
719
+ //--maxguid to show max guid
720
+ const inputParam = input.match(/--byrule .*/gm)
721
+ let showMaxGuid = false
722
+ let showMinGuid = false
723
+ if (inputParam != null && inputParam.length > 0 && inputParam[0].indexOf("--maxguid") > -1)
724
+ showMaxGuid = true
725
+
726
+ if (inputParam != null && inputParam.length > 0 && inputParam[0].indexOf("--minguid") > -1)
727
+ showMinGuid = true
728
+
729
+ const pageSize = 10000 //maximum messages per page
730
+ logger.info(`Querying attribute rules logs for ${parameters.service} ...`)
731
+ let result= await adminLog.query([102003], [parameters.service + ".MapServer"], pageSize)
732
+ let jsonRes = await result.json()
733
+ let allMessages = [].concat(jsonRes.logMessages)
734
+
735
+ while (jsonRes.hasMore)
736
+ {
737
+ //start paging
738
+ logger.info(`Aggregating messages... total so far ${allMessages.length} debug entries but more left, pulling logs before ${new Date(jsonRes.endTime)}`)
739
+ result= await adminLog.query([102003], [parameters.service + ".MapServer"], pageSize, jsonRes.endTime)
740
+ jsonRes = await result.json()
741
+ allMessages = allMessages.concat(jsonRes.logMessages)
742
+ }
743
+
744
+ const arMessages = allMessages
717
745
  .filter(m => m.message.indexOf("Attribute rule execution complete:") > -1)
718
746
  .map (m => JSON.parse(m.message.replace("Attribute rule execution complete:", "")))
719
747
  .map( m => {
@@ -729,30 +757,62 @@ const inputs = {
729
757
  .sort( (m1, m2) => m2["Elapsed Time (ms)"]- m1["Elapsed Time (ms)"])
730
758
  .reduce( ( prev, cur ) => {
731
759
  if (prev [cur["Rule name"]] === undefined)
732
- prev [cur["Rule name"]] = 0;
760
+ {
761
+ prev [cur["Rule name"]] = {
762
+ "totalTime": 0,
763
+ "occurrence": 0,
764
+ "minTime": Number.MAX_SAFE_INTEGER,
765
+ "maxTime": -1,
766
+ "avgTime": 0,
767
+ "maxGuid": null,
768
+ "minGuid": null
769
+ };
770
+ }
771
+
772
+ prev [cur["Rule name"]].totalTime = prev [cur["Rule name"]].totalTime + cur["Elapsed Time (ms)"]
773
+
774
+ if (cur["Elapsed Time (ms)"] < prev [cur["Rule name"]].minTime ) {
775
+ prev [cur["Rule name"]].minTime = cur["Elapsed Time (ms)"];
776
+ prev [cur["Rule name"]].minGuid = cur["GlobalID"];
777
+ }
778
+
779
+ if (cur["Elapsed Time (ms)"] > prev [cur["Rule name"]].maxTime ){
780
+ prev [cur["Rule name"]].maxTime = cur["Elapsed Time (ms)"];
781
+ prev [cur["Rule name"]].maxGuid = cur["GlobalID"];
782
+ }
783
+
784
+ prev [cur["Rule name"]].occurrence++
785
+
786
+ prev [cur["Rule name"]].avgTime = prev [cur["Rule name"]].totalTime / prev [cur["Rule name"]].occurrence
733
787
 
734
- prev [cur["Rule name"]] = cur["Elapsed Time (ms)"]
735
788
  return prev
736
789
  }, {})
737
790
 
738
791
  const rules = Object.keys(arMessages)
739
792
  .map(a => {
740
-
741
793
  const rule = {}
742
794
  rule["Attribute Rule"] = a;
743
- rule["Total Cost (ms)"] = arMessages[a];
795
+ rule["Total Cost (ms)"] = parseFloat(arMessages[a].totalTime.toFixed(2))
796
+ if (!showMinGuid && !showMaxGuid) rule["Average Cost (ms)"] = parseFloat(arMessages[a].avgTime.toFixed(2))
797
+ rule["Max execution time (ms)"] = parseFloat(arMessages[a].maxTime.toFixed(2))
798
+ if (showMaxGuid) rule["Max GUID"] = arMessages[a].maxGuid
799
+ rule["Min execution time (ms)"] = parseFloat(arMessages[a].minTime.toFixed(2))
800
+ if (showMinGuid) rule["Min GUID"] = arMessages[a].minGuid
801
+ if (!showMinGuid && !showMaxGuid) rule["Occurrence"] = arMessages[a].occurrence;
744
802
  return rule;
745
803
  })
746
804
  .sort( (m1, m2) => m2["Total Cost (ms)"] -m1["Total Cost (ms)"])
747
805
  console.table(rules)
748
-
806
+
807
+ const totalARExecution = rules.reduce( (prev, cur) => prev + cur["Total Cost (ms)"], 0)
808
+ console.log(`Total time spend executing attribute rules (${Math.round(totalARExecution)} ms) (${Math.round(totalARExecution/1000)} s) (${Math.round(totalARExecution/(1000*60))} m)`)
749
809
 
750
810
  },
751
811
 
752
812
  "^version$": () => console.log(version),
753
813
  "^clear$|^cls$": () => console.clear(),
754
814
  "^quit$": () => {
755
- rl.close();
815
+ if (rl) rl.close();
756
816
  process.exit();
757
817
  },
758
818
  "^exit$|^quit$|^bye$": () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "un-cli",
3
- "version": "0.0.58",
3
+ "version": "0.0.61",
4
4
  "description": "Command line interface for working with ArcGIS Utility Network Extension",
5
5
  "main": "index.mjs",
6
6
  "bin": {
package/portal.mjs CHANGED
@@ -3,13 +3,13 @@ import { logger } from "./logger.mjs"
3
3
 
4
4
  export class Portal{
5
5
 
6
- constructor(url, username, password, expiration = 300)
6
+ constructor(url, username, password, expiration = 300, serverUrl = undefined)
7
7
  {
8
8
  this.url = url;
9
9
  this.username = username;
10
10
  this.password = password;
11
11
  this.expiration = expiration;
12
- this.serverUrl = "bad";
12
+ this.serverUrl = serverUrl;
13
13
  }
14
14
 
15
15
 
@@ -20,7 +20,6 @@ export class Portal{
20
20
 
21
21
  try {
22
22
 
23
- //https://utilitynetwork.esri.com/portal/sharing/rest/portals/self/servers?f=json
24
23
  const tokenUrl = self.url + "/sharing/rest/generateToken";
25
24
 
26
25
  const postJson = {
@@ -44,9 +43,9 @@ export class Portal{
44
43
 
45
44
  }
46
45
  catch(ex){
47
- logger.error(ex.status.message)
48
- console.error(ex.status.errno)
49
- reject(`Failed to connect to portal, check your username or password or add --verify false if you are using a self-signed certificate. Normally a production system should have a valid certificate signed by a CA and you should NOT disable verification in that case.)`)
46
+ logger.error(ex?.status?.message)
47
+ console.error(ex?.status?.errno)
48
+ reject(`Failed to connect to portal, check your username or password or add --verify false if you are using a self-signed certificate. Normally a production system should have a valid certificate signed by a CA and you should NOT disable verification in that case.) \n\n${ex}`)
50
49
  }
51
50
 
52
51
  }
@@ -55,32 +54,54 @@ export class Portal{
55
54
  }
56
55
 
57
56
  async updateServices () {
57
+ const self = this;
58
+ return new Promise( async (resolve, reject) => {
59
+ //if the user specified a serverUrl no need to do anything
60
+ if (self.serverUrl !== undefined) {
61
+ logger.info(`Using server ${self.serverUrl} supplied in the --server parameter`)
62
+ resolve(self.serverUrl);
63
+ return;
64
+ }
58
65
 
59
- try {
66
+ //else we need to calculate it
67
+ try {
60
68
 
61
- const postJsonServers= {
62
- f: "json",
63
- token: this.token
69
+ const postJsonServers= {
70
+ f: "json",
71
+ token: self.token
72
+ }
73
+
74
+ const serversUrl = self.url + "/sharing/rest/portals/self/servers"
75
+ logger.info( "About to query federated servers");
76
+
77
+ //query for federated servers.
78
+ const servers = await makeRequest({method: 'POST', url: serversUrl, params: postJsonServers });
79
+
80
+ //if we don't have any federated servers quit.
81
+ if (servers.servers.length === 0)
82
+ {
83
+ reject( "No federeated servers");
84
+ return
85
+ }
86
+
87
+ //if we have more than one then we let the user pick.
88
+ if (servers.servers.length > 1){
89
+ let serverUrls = "";
90
+ servers.servers.forEach(s => serverUrls += '\n * ' + s.url + '\n' )
91
+ reject("more than one federated server found, run the command with --server and specify one of the servers below\n" + serverUrls)
92
+ }
93
+ this.serverUrl = servers.servers[0].url;
94
+ resolve(self.serverUrl)
95
+ logger.info(`Found one federate server, using server url ${self.serverUrl} by default`)
64
96
  }
65
-
66
- const serversUrl = this.url + "/sharing/rest/portals/self/servers"
67
- logger.info( "About to query federated servers");
68
-
69
- //query for federated servers.
70
- const servers = await makeRequest({method: 'POST', url: serversUrl, params: postJsonServers });
71
-
72
- //get the first one
73
- if (servers.servers.length === 0)
74
- reject( "No federeated servers");
75
-
76
- this.serverUrl = servers.servers[0].url;
77
- logger.info(`Found server url ${this.serverUrl}`)
78
- }
79
- catch(ex){
80
- logger.error(ex)
81
- }
97
+ catch(ex){
98
+ logger.error(ex)
99
+ reject(ex)
100
+ }
101
+ });
102
+
103
+ }
82
104
 
83
- }
84
105
  //get the feature service definition
85
106
  async serviceDef(serviceName) {
86
107
  this.token = this.token;
@@ -109,9 +130,20 @@ export class Portal{
109
130
  token: this.token,
110
131
  f: "json"
111
132
  }
112
-
133
+
134
+ let allServices = []
113
135
  const services = await makeRequest({method: 'POST', url: servicesUrl, params: postJson })
114
- resolve(services);
136
+ allServices = allServices.concat(services.services);
137
+ const folders = services.folders;
138
+ for (let f = 0; f < folders.length; f++)
139
+ {
140
+ const folderUrl = servicesUrl + "/" + folders[f];
141
+ const folderServices = await makeRequest({method: 'POST', url: folderUrl, params: postJson })
142
+ allServices = allServices.concat(folderServices.services)
143
+ }
144
+
145
+
146
+ resolve({"services": allServices});
115
147
  }
116
148
  catch(ex) {
117
149
  reject(ex)