un-cli 0.0.65 → 0.0.68
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 +37 -19
- package/cmd.txt +1 -2
- package/index.mjs +42 -27
- package/package.json +1 -1
- package/utilitynetwork.node.mjs +37 -3
package/README.md
CHANGED
|
@@ -18,26 +18,44 @@ npm install -g un-cli
|
|
|
18
18
|
```
|
|
19
19
|
|
|
20
20
|
```bash
|
|
21
|
+
|
|
21
22
|
uncli> help
|
|
22
|
-
|
|
23
|
-
│
|
|
24
|
-
|
|
25
|
-
│
|
|
26
|
-
│
|
|
27
|
-
│
|
|
28
|
-
│
|
|
29
|
-
│
|
|
30
|
-
│
|
|
31
|
-
│
|
|
32
|
-
│
|
|
33
|
-
│
|
|
34
|
-
│
|
|
35
|
-
│
|
|
36
|
-
│
|
|
37
|
-
│
|
|
38
|
-
│
|
|
39
|
-
│
|
|
40
|
-
|
|
23
|
+
┌───────────────────────────────────────┬──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
|
|
24
|
+
│ (index) │ Values │
|
|
25
|
+
├───────────────────────────────────────┼──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┤
|
|
26
|
+
│ help │ 'Displays this help' │
|
|
27
|
+
│ version │ 'Displays the version of uncli' │
|
|
28
|
+
│ ls │ 'List all services' │
|
|
29
|
+
│ def │ 'Show the feature service definition' │
|
|
30
|
+
│ def --layers │ 'List all layers in this service' │
|
|
31
|
+
│ subnetworks │ 'Lists all subnetworks' │
|
|
32
|
+
│ subnetworks --dirty │ 'Lists only dirty subnetworks' │
|
|
33
|
+
│ subnetworks --deleted │ 'Lists dirty and deleted subnetworks' │
|
|
34
|
+
│ evaluate │ 'Evaluate in parallel' │
|
|
35
|
+
│ trace --subnetwork <subnetwork> │ 'Traces input subnetwork and returns the time and number of elements returned .' │
|
|
36
|
+
│ topology │ 'Displays the topology status' │
|
|
37
|
+
│ topology --disable │ 'Disable topology' │
|
|
38
|
+
│ topology --enable │ 'Enable topology' │
|
|
39
|
+
│ topology --validate │ 'Validate topology (full extent)' │
|
|
40
|
+
│ update subnetworks --all │ 'Update all dirty subnetworks synchronously' │
|
|
41
|
+
│ update subnetworks --deleted │ 'Update all deleted dirty subnetworks synchronously' │
|
|
42
|
+
│ update subnetworks --all --async │ 'Update all dirty subnetworks asynchronously' │
|
|
43
|
+
│ export subnetworks --all │ 'Export all subnetworks with ACK ' │
|
|
44
|
+
│ export subnetworks --new │ "Export all subnetworks with ACK that haven't been exported " │
|
|
45
|
+
│ export subnetworks --deleted │ 'Export all subnetworks with ACK that are deleted ' │
|
|
46
|
+
│ count │ 'Lists the number of rows in all feature layers.' │
|
|
47
|
+
│ count --system │ 'Lists the number of rows in system layers.' │
|
|
48
|
+
│ connect --service │ 'Connects to the another service' │
|
|
49
|
+
│ tracelogs --age <minutes> │ 'Lists utility network trace summary logs for the last x minutes (requires admin)' │
|
|
50
|
+
│ validatelogs --age <minutes> │ 'Lists utility network validate summary logs for the last x minutes (requires admin)' │
|
|
51
|
+
│ updatesubnetworkslog --age <minutes> │ 'Lists utility network update subnetworks summary logs for the last x minutes (requires admin)' │
|
|
52
|
+
│ arlogs --age <minutes> │ 'Lists attribute rules execution logs for the last x minutes (requires admin)' │
|
|
53
|
+
│ arlogs --byrule [--minguid --maxguid] │ 'Lists attribute rules execution summary by rule (requires admin), --maxguid and --minguid show the GUID of the feature' │
|
|
54
|
+
│ topsql --age <minutes> │ 'Lists all queries executed in the last x minutes (requires admin)' │
|
|
55
|
+
│ whoami │ 'Lists the current login info' │
|
|
56
|
+
│ clear │ 'Clears this screen' │
|
|
57
|
+
│ quit │ 'Exit this program' │
|
|
58
|
+
└───────────────────────────────────────┴──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
|
|
41
59
|
```
|
|
42
60
|
|
|
43
61
|
|
package/cmd.txt
CHANGED
package/index.mjs
CHANGED
|
@@ -7,7 +7,7 @@ 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.
|
|
10
|
+
let version = "0.0.68";
|
|
11
11
|
const GENERATE_TOKEN_TIME_MIN = 30;
|
|
12
12
|
|
|
13
13
|
let rl = null;
|
|
@@ -172,8 +172,8 @@ const inputs = {
|
|
|
172
172
|
"update subnetworks --all": "Update all dirty subnetworks synchronously",
|
|
173
173
|
"update subnetworks --deleted": "Update all deleted dirty subnetworks synchronously",
|
|
174
174
|
"update subnetworks --all --async": "Update all dirty subnetworks asynchronously",
|
|
175
|
-
"export subnetworks --all": "Export all subnetworks with ACK ",
|
|
176
|
-
"export subnetworks --new": "Export all subnetworks with ACK that haven't been exported ",
|
|
175
|
+
"export subnetworks --all [--folder]": "Export all subnetworks with ACK --folder where exported files are saved",
|
|
176
|
+
"export subnetworks --new [--folder]": "Export all subnetworks with ACK that haven't been exported --folder where exported files are saved",
|
|
177
177
|
"export subnetworks --deleted": "Export all subnetworks with ACK that are deleted ",
|
|
178
178
|
"count": "Lists the number of rows in all feature layers.",
|
|
179
179
|
"count --system": "Lists the number of rows in system layers.",
|
|
@@ -247,7 +247,7 @@ const inputs = {
|
|
|
247
247
|
// console.log('\x1b[36m%s\x1b[0m', 'I am cyan'); //cyan
|
|
248
248
|
const topoMoments = networkMoments.networkMoments.map(m => {
|
|
249
249
|
const t = m.time === 0 ? "N/A": new Date(m.time*1000)
|
|
250
|
-
const d = m.duration === 0 ? "N/A" : numberWithCommas(Math.round(m.duration
|
|
250
|
+
const d = m.duration === 0 ? "N/A" : numberWithCommas(Math.round(m.duration)) + " ms"
|
|
251
251
|
m.time = t.toString()
|
|
252
252
|
m.duration = d;
|
|
253
253
|
return m;
|
|
@@ -265,7 +265,7 @@ const inputs = {
|
|
|
265
265
|
const result = await un.enableTopology()
|
|
266
266
|
const toDate = new Date();
|
|
267
267
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
268
|
-
result.duration = numberWithCommas(Math.round(timeEnable
|
|
268
|
+
result.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
269
269
|
console.table(result)
|
|
270
270
|
},
|
|
271
271
|
"^topology --disable$": async () => {
|
|
@@ -274,7 +274,7 @@ const inputs = {
|
|
|
274
274
|
const result = await un.disableTopology()
|
|
275
275
|
const toDate = new Date();
|
|
276
276
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
277
|
-
result.duration = numberWithCommas(Math.round(timeEnable
|
|
277
|
+
result.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
278
278
|
console.table(result)
|
|
279
279
|
},
|
|
280
280
|
"^evaluate$": async () => {
|
|
@@ -296,7 +296,7 @@ const inputs = {
|
|
|
296
296
|
const result = {}
|
|
297
297
|
const toDate = new Date();
|
|
298
298
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
299
|
-
result.duration = numberWithCommas(Math.round(timeEnable
|
|
299
|
+
result.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
300
300
|
console.log(result)
|
|
301
301
|
},
|
|
302
302
|
|
|
@@ -341,7 +341,7 @@ const inputs = {
|
|
|
341
341
|
const result = await un.validateNetworkTopology("sde.DEFAULT", e)
|
|
342
342
|
const toDate = new Date();
|
|
343
343
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
344
|
-
const duration = numberWithCommas(Math.round(timeEnable
|
|
344
|
+
const duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
345
345
|
console.clear()
|
|
346
346
|
console.log("Validating extent " + e.xmin)
|
|
347
347
|
console.table({duration})
|
|
@@ -357,7 +357,7 @@ const inputs = {
|
|
|
357
357
|
const result = await un.validateNetworkTopology()
|
|
358
358
|
const toDate = new Date();
|
|
359
359
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
360
|
-
result.duration = numberWithCommas(Math.round(timeEnable
|
|
360
|
+
result.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
361
361
|
console.table(result)
|
|
362
362
|
},
|
|
363
363
|
"^subnetworks --dirty$": async () => {
|
|
@@ -387,7 +387,7 @@ const inputs = {
|
|
|
387
387
|
|
|
388
388
|
"^update subnetworks --deleted$" : async () => {
|
|
389
389
|
console.log("Querying all subnetworks that are dirty and deleted.");
|
|
390
|
-
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1 and isdeleted=1");
|
|
390
|
+
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1 and isdeleted=1","domainnetworkname,tiername,subnetworkname");
|
|
391
391
|
console.log(`Discovered ${subnetworks.features.length} dirty deleted subnetworks.`);
|
|
392
392
|
for (let i = 0; i < subnetworks.features.length; i++) {
|
|
393
393
|
const f = subnetworks.features[i]
|
|
@@ -402,7 +402,7 @@ const inputs = {
|
|
|
402
402
|
|
|
403
403
|
const toDate = new Date();
|
|
404
404
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
405
|
-
subnetworkResult.duration = numberWithCommas(Math.round(timeEnable
|
|
405
|
+
subnetworkResult.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
406
406
|
|
|
407
407
|
|
|
408
408
|
console.log(`Result ${JSON.stringify(subnetworkResult)}`)
|
|
@@ -411,7 +411,7 @@ const inputs = {
|
|
|
411
411
|
|
|
412
412
|
"^update subnetworks --all$" : async () => {
|
|
413
413
|
console.log("Querying all subnetworks that are dirty.");
|
|
414
|
-
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1");
|
|
414
|
+
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1", "domainnetworkname,tiername,subnetworkname");
|
|
415
415
|
console.log(`Discovered ${subnetworks.features.length} dirty subnetworks.`);
|
|
416
416
|
|
|
417
417
|
|
|
@@ -426,14 +426,14 @@ const inputs = {
|
|
|
426
426
|
|
|
427
427
|
const toDate = new Date();
|
|
428
428
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
429
|
-
subnetworkResult.duration = numberWithCommas(Math.round(timeEnable
|
|
429
|
+
subnetworkResult.duration = numberWithCommas(Math.round(timeEnable)) + " ms"
|
|
430
430
|
|
|
431
431
|
console.log(`Result ${JSON.stringify(subnetworkResult)}`)
|
|
432
432
|
}
|
|
433
433
|
},
|
|
434
434
|
"^update subnetworks --all --async$" : async () => {
|
|
435
435
|
console.log("Querying all subnetworks that are dirty.");
|
|
436
|
-
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1");
|
|
436
|
+
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=1", "domainnetworkname,tiername,subnetworkname");
|
|
437
437
|
console.log(`Discovered ${subnetworks.features.length} dirty subnetworks.`);
|
|
438
438
|
for (let i = 0; i < subnetworks.features.length; i++) {
|
|
439
439
|
const f = subnetworks.features[i]
|
|
@@ -442,20 +442,20 @@ const inputs = {
|
|
|
442
442
|
console.log(`Result from submitting job ${JSON.stringify(subnetworkResult)}`)
|
|
443
443
|
}
|
|
444
444
|
},
|
|
445
|
-
|
|
445
|
+
"^export subnetworks --all --folder .*$|^export subnetworks --all$" : async input => {
|
|
446
446
|
|
|
447
447
|
|
|
448
448
|
//create folder
|
|
449
|
-
const file = input.match(
|
|
449
|
+
const file = input.match(/--folder .*/gm)
|
|
450
450
|
let inputDir = "Exported"
|
|
451
451
|
if (file != null && file.length > 0)
|
|
452
|
-
inputDir = file[0].replace("
|
|
452
|
+
inputDir = file[0].replace("--folder ", "")
|
|
453
453
|
//create directory if doesn't exists
|
|
454
454
|
if (!fs.existsSync(inputDir)) fs.mkdirSync(inputDir)
|
|
455
455
|
|
|
456
456
|
|
|
457
457
|
console.log("Querying all subnetworks that are clean.");
|
|
458
|
-
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=0");
|
|
458
|
+
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty=0","domainnetworkname,tiername,subnetworkname");
|
|
459
459
|
console.log(`Discovered ${subnetworks.features.length} subnetworks that can be exported.`);
|
|
460
460
|
for (let i = 0; i < subnetworks.features.length; i++) {
|
|
461
461
|
const f = subnetworks.features[i]
|
|
@@ -471,8 +471,13 @@ const inputs = {
|
|
|
471
471
|
|
|
472
472
|
const toDate = new Date();
|
|
473
473
|
const timeEnable = toDate.getTime() - fromDate.getTime();
|
|
474
|
-
subnetworkResult.duration = numberWithCommas(
|
|
475
|
-
|
|
474
|
+
subnetworkResult.duration = numberWithCommas(timeEnable) + " ms"
|
|
475
|
+
//if undefined exit
|
|
476
|
+
if (!subnetworkResult.url)
|
|
477
|
+
{
|
|
478
|
+
console.log("Export subnetwork failed " + JSON.stringify(subnetworkResult))
|
|
479
|
+
continue;
|
|
480
|
+
}
|
|
476
481
|
|
|
477
482
|
//fetch the json and write it to disk
|
|
478
483
|
const subContent = await fetch(subnetworkResult.url);
|
|
@@ -501,7 +506,7 @@ const inputs = {
|
|
|
501
506
|
|
|
502
507
|
|
|
503
508
|
console.log("Querying all subnetworks that are clean and not exported.");
|
|
504
|
-
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty = 0 and (LASTACKEXPORTSUBNETWORK is null or LASTACKEXPORTSUBNETWORK < LASTUPDATESUBNETWORK)");
|
|
509
|
+
let subnetworks = await un.queryDistinct(500002, "domainnetworkname,tiername,subnetworkname", "isdirty = 0 and (LASTACKEXPORTSUBNETWORK is null or LASTACKEXPORTSUBNETWORK < LASTUPDATESUBNETWORK)","domainnetworkname,tiername,subnetworkname");
|
|
505
510
|
console.log(`Discovered ${subnetworks.features.length} subnetworks that can be exported.`);
|
|
506
511
|
for (let i = 0; i < subnetworks.features.length; i++) {
|
|
507
512
|
const f = subnetworks.features[i]
|
|
@@ -555,7 +560,7 @@ const inputs = {
|
|
|
555
560
|
const toDate = new Date();
|
|
556
561
|
const timeRun = toDate.getTime() - fromDate.getTime();
|
|
557
562
|
const newResult = {}
|
|
558
|
-
newResult.duration = numberWithCommas(Math.round(timeRun
|
|
563
|
+
newResult.duration = numberWithCommas(Math.round(timeRun)) + " ms"
|
|
559
564
|
newResult.elementsCount = result.traceResults.elements.length;
|
|
560
565
|
console.table(newResult)
|
|
561
566
|
|
|
@@ -579,6 +584,14 @@ const inputs = {
|
|
|
579
584
|
const subnetworkName = v(f.attributes,"subnetworkName")
|
|
580
585
|
console.log(`Exporting subnetwork ${subnetworkName}` );
|
|
581
586
|
const subnetworkResult = await un.exportSubnetworks(v(f.attributes,"domainNetworkName"), v(f.attributes,"tierName"), v(f.attributes,"subnetworkName"),false);
|
|
587
|
+
|
|
588
|
+
//if undefined exit
|
|
589
|
+
if (!subnetworkResult.url)
|
|
590
|
+
{
|
|
591
|
+
console.log("Export subnetwork failed " + JSON.stringify(subnetworkResult))
|
|
592
|
+
continue;
|
|
593
|
+
}
|
|
594
|
+
|
|
582
595
|
//fetch the json and write it to disk
|
|
583
596
|
const subContent = await fetch(subnetworkResult.url);
|
|
584
597
|
const jsonExport = await subContent.text();
|
|
@@ -889,15 +902,16 @@ const inputs = {
|
|
|
889
902
|
allMessages = allMessages.concat(jsonRes.logMessages.filter(m => m.message.indexOf("EndCursor;") > -1))
|
|
890
903
|
}
|
|
891
904
|
console.log ("Filtering messages...")
|
|
905
|
+
|
|
892
906
|
allMessages = allMessages
|
|
893
907
|
.map( m=> {
|
|
894
908
|
m.dataAccessElapsed = parseFloat(m.message.split(";")[1].split(" ")[1])
|
|
895
|
-
m.
|
|
896
|
-
m.
|
|
909
|
+
m.executeQueryElapsed = parseFloat(m.message.split(";")[2].split(" ")[1])
|
|
910
|
+
m.totalExecutionElapsed = m.dataAccessElapsed + m.executeQueryElapsed
|
|
897
911
|
m.elapsed = parseFloat(m.elapsed); return m;
|
|
898
912
|
|
|
899
913
|
})
|
|
900
|
-
.sort( (m1,m2) => m2.
|
|
914
|
+
.sort( (m1,m2) => m2.totalExecutionElapsed - m1.totalExecutionElapsed)
|
|
901
915
|
.slice(0, 10) ;//first 10
|
|
902
916
|
|
|
903
917
|
|
|
@@ -907,11 +921,12 @@ const inputs = {
|
|
|
907
921
|
{
|
|
908
922
|
|
|
909
923
|
const x = m.message.split(";")
|
|
924
|
+
x.shift()
|
|
910
925
|
console.log(`id: ${i++}`)
|
|
911
926
|
console.log(`\tAt: ${new Date(m.time)} (${m.time})`)
|
|
912
927
|
console.log(`\tUser: ${m.user}`)
|
|
913
|
-
console.log(`\
|
|
914
|
-
console.log(`\
|
|
928
|
+
console.log(`\tTotal Time: ${numberWithCommas(Math.round(m.elapsed*1000))} ms (Total time the cursor was opened)`)
|
|
929
|
+
console.log(`\tQuery Time: ${numberWithCommas(m.totalExecutionElapsed)} ms (includes search + data access nextRow)`)
|
|
915
930
|
console.log(`\tQuery:`)
|
|
916
931
|
x.forEach(a => console.log(`\t${a}`))
|
|
917
932
|
console.log(`\n`)
|
package/package.json
CHANGED
package/utilitynetwork.node.mjs
CHANGED
|
@@ -275,7 +275,7 @@ export class UtilityNetwork {
|
|
|
275
275
|
|
|
276
276
|
}
|
|
277
277
|
|
|
278
|
-
queryDistinct(layerId, field, where) {
|
|
278
|
+
queryDistinct(layerId, field, where, orderby) {
|
|
279
279
|
|
|
280
280
|
let queryJson = {
|
|
281
281
|
f: "json",
|
|
@@ -283,7 +283,9 @@ export class UtilityNetwork {
|
|
|
283
283
|
outFields: field,
|
|
284
284
|
where: where,
|
|
285
285
|
gdbVersion:this.gdbVersion,
|
|
286
|
-
returnDistinctValues: true
|
|
286
|
+
returnDistinctValues: true,
|
|
287
|
+
orderByFields: orderby,
|
|
288
|
+
|
|
287
289
|
}
|
|
288
290
|
|
|
289
291
|
queryJson.layerId = layerId
|
|
@@ -997,6 +999,37 @@ export class UtilityNetwork {
|
|
|
997
999
|
let ar = thisObj.featureServiceUrl.split("/");
|
|
998
1000
|
ar[ar.length-1]="UtilityNetworkServer";
|
|
999
1001
|
let exportsubnetworkUrl = ar.join("/") + "/exportSubnetwork"
|
|
1002
|
+
const resultTypes = [
|
|
1003
|
+
{
|
|
1004
|
+
"type": "features",
|
|
1005
|
+
"includeGeometry": true,
|
|
1006
|
+
"includePropagatedValues": false,
|
|
1007
|
+
"includeDomainDescriptions": true,
|
|
1008
|
+
"networkAttributeNames": [
|
|
1009
|
+
"Is subnetwork controller"
|
|
1010
|
+
],
|
|
1011
|
+
"diagramTemplateName": "",
|
|
1012
|
+
"resultTypeFields": []
|
|
1013
|
+
},
|
|
1014
|
+
{
|
|
1015
|
+
"type": "connectivity",
|
|
1016
|
+
"includeGeometry": true,
|
|
1017
|
+
"includePropagatedValues": false,
|
|
1018
|
+
"includeDomainDescriptions": true,
|
|
1019
|
+
"networkAttributeNames": [],
|
|
1020
|
+
"diagramTemplateName": "",
|
|
1021
|
+
"resultTypeFields": []
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
"type": "associations",
|
|
1025
|
+
"includeGeometry": false,
|
|
1026
|
+
"includePropagatedValues": false,
|
|
1027
|
+
"includeDomainDescriptions": true,
|
|
1028
|
+
"networkAttributeNames": [],
|
|
1029
|
+
"diagramTemplateName": "",
|
|
1030
|
+
"resultTypeFields": []
|
|
1031
|
+
}
|
|
1032
|
+
]
|
|
1000
1033
|
//traceConfiguration: JSON.stringify(subnetworkDef),
|
|
1001
1034
|
let exportsubnetworkJson = {
|
|
1002
1035
|
f: "json",
|
|
@@ -1007,9 +1040,10 @@ export class UtilityNetwork {
|
|
|
1007
1040
|
exportAcknowledgement: true,
|
|
1008
1041
|
allSubnetworksInTier: false,
|
|
1009
1042
|
continueOnFailure: false,
|
|
1010
|
-
traceConfiguration: subnetworkDef,
|
|
1043
|
+
traceConfiguration: subnetworkDef,
|
|
1011
1044
|
async: async,
|
|
1012
1045
|
gdbVersion:this.gdbVersion,
|
|
1046
|
+
resultTypes: JSON.stringify(resultTypes)
|
|
1013
1047
|
}
|
|
1014
1048
|
let un = this;
|
|
1015
1049
|
|