Merge branch 'master' into spencer/pwa
# Conflicts: # package-lock.json # src/app/app.component.ts # src/index.html
This commit is contained in:
commit
d914f6b2b8
@ -1,5 +1,6 @@
|
||||
# Logging levels => TRACE = 0|DEBUG = 1|INFO = 2|LOG = 3|WARN = 4|ERROR = 5|FATAL = 6|OFF = 7
|
||||
LOG_LEVEL=
|
||||
SERVER_LEVEL=
|
||||
SERVER_LOG_LEVEL=
|
||||
CIC_CHAIN_ID=
|
||||
CIC_LOGGING_URL=
|
||||
CIC_META_URL=
|
||||
|
229
package-lock.json
generated
229
package-lock.json
generated
@ -18,7 +18,6 @@
|
||||
"@angular/platform-browser": "~10.2.0",
|
||||
"@angular/platform-browser-dynamic": "~10.2.0",
|
||||
"@angular/router": "~10.2.0",
|
||||
"@angular/service-worker": "~10.2.0",
|
||||
"@popperjs/core": "^2.5.4",
|
||||
"angular-datatables": "^9.0.2",
|
||||
"block-syncer": "^0.2.4",
|
||||
@ -29,7 +28,6 @@
|
||||
"datatables.net": "^1.10.22",
|
||||
"datatables.net-dt": "^1.10.22",
|
||||
"ethers": "^5.0.31",
|
||||
"http-server": "^0.12.3",
|
||||
"jquery": "^3.5.1",
|
||||
"mocha": "^8.2.1",
|
||||
"moolb": "^0.1.0",
|
||||
@ -513,15 +511,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/common": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.4.tgz",
|
||||
"integrity": "sha512-bBfsLJNDQaC2OI1mReDJuSZ/uBb7Pf3HVpRmlQKNIPllIxqX1hLH8I3Plodrns9m32JMJ6FMsQthcP0KMdRCJA==",
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.1.tgz",
|
||||
"integrity": "sha512-aJtgokgWxibd7wGmktHm0uYkR/lOrbcStrn6Qisj/PIJf9xTGXYFB0yusnk103aiuBfCIKq+Wl0ZGc1s81Okaw==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/core": "10.2.4",
|
||||
"rxjs": "^6.5.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/compiler": {
|
||||
@ -776,15 +770,11 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/core": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.4.tgz",
|
||||
"integrity": "sha512-5xpAvmZwD9nZ8eWx10urjibqEeePGEiFXVMEn3IaJWgfdOcMmeSoioW9JUllT3w85+DlNVWbRbhz0YfE9a4jyw==",
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.1.tgz",
|
||||
"integrity": "sha512-zt9G5Ei1nxB6yVJqpiH7K6npaiEUrPWlDCq6vwXeJbmO3tbw2WWiqD55Wkx5hRfysY43swC5j7VveNytHidkkQ==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"rxjs": "^6.5.3",
|
||||
"zone.js": "~0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/forms": {
|
||||
@ -827,21 +817,6 @@
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@angular/service-worker": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-10.2.4.tgz",
|
||||
"integrity": "sha512-1miQ5iNKPDelY11qpsU/4LyQcZce5zTRoYj8Qw7JfFQo9NG01HxcQs+FXoFbs1ZggmUIJS9L2C3++Sp9hVhu+A==",
|
||||
"dependencies": {
|
||||
"tslib": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"ngsw-config": "ngsw-config.js"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@angular/common": "10.2.4",
|
||||
"@angular/core": "10.2.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
@ -4055,6 +4030,7 @@
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
@ -4324,14 +4300,6 @@
|
||||
"node": "^4.5.0 || >= 5.9"
|
||||
}
|
||||
},
|
||||
"node_modules/basic-auth": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
|
||||
"integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ=",
|
||||
"engines": {
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/batch": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||
@ -5876,6 +5844,7 @@
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=0.1.90"
|
||||
}
|
||||
@ -6219,14 +6188,6 @@
|
||||
"node": ">= 0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c=",
|
||||
"engines": {
|
||||
"node": ">= 0.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/cosmiconfig": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
|
||||
@ -7200,20 +7161,6 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ecstatic": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
|
||||
"integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
|
||||
"dependencies": {
|
||||
"he": "^1.1.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.1.0",
|
||||
"url-join": "^2.0.5"
|
||||
},
|
||||
"bin": {
|
||||
"ecstatic": "lib/ecstatic.js"
|
||||
}
|
||||
},
|
||||
"node_modules/ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -7848,7 +7795,8 @@
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/events": {
|
||||
"version": "3.2.0",
|
||||
@ -8451,6 +8399,7 @@
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=4.0"
|
||||
}
|
||||
@ -9139,6 +9088,7 @@
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
@ -9309,30 +9259,6 @@
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/http-server": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
|
||||
"integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
|
||||
"dependencies": {
|
||||
"basic-auth": "^1.0.3",
|
||||
"colors": "^1.4.0",
|
||||
"corser": "^2.0.1",
|
||||
"ecstatic": "^3.3.2",
|
||||
"http-proxy": "^1.18.0",
|
||||
"minimist": "^1.2.5",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.25",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"hs": "bin/http-server",
|
||||
"http-server": "bin/http-server"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
@ -13451,14 +13377,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
|
||||
"bin": {
|
||||
"opener": "bin/opener-bin.js"
|
||||
}
|
||||
},
|
||||
"node_modules/openpgp": {
|
||||
"version": "4.10.10",
|
||||
"resolved": "https://registry.npmjs.org/openpgp/-/openpgp-4.10.10.tgz",
|
||||
@ -14210,6 +14128,7 @@
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||
"integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"async": "^2.6.2",
|
||||
"debug": "^3.1.1",
|
||||
@ -14223,6 +14142,7 @@
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
@ -16008,7 +15928,8 @@
|
||||
"node_modules/requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/resolve": {
|
||||
"version": "1.18.1",
|
||||
@ -16423,11 +16344,6 @@
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM="
|
||||
},
|
||||
"node_modules/select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@ -18500,17 +18416,6 @@
|
||||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"dependencies": {
|
||||
"qs": "^6.4.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
@ -18669,11 +18574,6 @@
|
||||
"querystring": "0.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/url-join": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg="
|
||||
},
|
||||
"node_modules/url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
@ -21177,9 +21077,9 @@
|
||||
}
|
||||
},
|
||||
"@angular/common": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.4.tgz",
|
||||
"integrity": "sha512-bBfsLJNDQaC2OI1mReDJuSZ/uBb7Pf3HVpRmlQKNIPllIxqX1hLH8I3Plodrns9m32JMJ6FMsQthcP0KMdRCJA==",
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/common/-/common-10.2.1.tgz",
|
||||
"integrity": "sha512-aJtgokgWxibd7wGmktHm0uYkR/lOrbcStrn6Qisj/PIJf9xTGXYFB0yusnk103aiuBfCIKq+Wl0ZGc1s81Okaw==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
@ -21381,9 +21281,9 @@
|
||||
}
|
||||
},
|
||||
"@angular/core": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.4.tgz",
|
||||
"integrity": "sha512-5xpAvmZwD9nZ8eWx10urjibqEeePGEiFXVMEn3IaJWgfdOcMmeSoioW9JUllT3w85+DlNVWbRbhz0YfE9a4jyw==",
|
||||
"version": "10.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@angular/core/-/core-10.2.1.tgz",
|
||||
"integrity": "sha512-zt9G5Ei1nxB6yVJqpiH7K6npaiEUrPWlDCq6vwXeJbmO3tbw2WWiqD55Wkx5hRfysY43swC5j7VveNytHidkkQ==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
@ -21428,14 +21328,6 @@
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@angular/service-worker": {
|
||||
"version": "10.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@angular/service-worker/-/service-worker-10.2.4.tgz",
|
||||
"integrity": "sha512-1miQ5iNKPDelY11qpsU/4LyQcZce5zTRoYj8Qw7JfFQo9NG01HxcQs+FXoFbs1ZggmUIJS9L2C3++Sp9hVhu+A==",
|
||||
"requires": {
|
||||
"tslib": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"@babel/code-frame": {
|
||||
"version": "7.12.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz",
|
||||
@ -24253,6 +24145,7 @@
|
||||
"version": "2.6.3",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz",
|
||||
"integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lodash": "^4.17.14"
|
||||
}
|
||||
@ -24478,11 +24371,6 @@
|
||||
"integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==",
|
||||
"dev": true
|
||||
},
|
||||
"basic-auth": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-1.1.0.tgz",
|
||||
"integrity": "sha1-RSIe5Cn37h5QNb4/UVM/HN/SmIQ="
|
||||
},
|
||||
"batch": {
|
||||
"version": "0.6.1",
|
||||
"resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz",
|
||||
@ -25801,7 +25689,8 @@
|
||||
"colors": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz",
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA=="
|
||||
"integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==",
|
||||
"dev": true
|
||||
},
|
||||
"combined-stream": {
|
||||
"version": "1.0.8",
|
||||
@ -26102,11 +25991,6 @@
|
||||
"vary": "^1"
|
||||
}
|
||||
},
|
||||
"corser": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/corser/-/corser-2.0.1.tgz",
|
||||
"integrity": "sha1-jtolLsqrWEDc2XXOuQ2TcMgZ/4c="
|
||||
},
|
||||
"cosmiconfig": {
|
||||
"version": "5.2.1",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz",
|
||||
@ -26928,17 +26812,6 @@
|
||||
"safer-buffer": "^2.1.0"
|
||||
}
|
||||
},
|
||||
"ecstatic": {
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/ecstatic/-/ecstatic-3.3.2.tgz",
|
||||
"integrity": "sha512-fLf9l1hnwrHI2xn9mEDT7KIi22UDqA2jaCwyCbSUJh9a1V+LEUSL/JO/6TIz/QyuBURWUHrFL5Kg2TtO1bkkog==",
|
||||
"requires": {
|
||||
"he": "^1.1.1",
|
||||
"mime": "^1.6.0",
|
||||
"minimist": "^1.1.0",
|
||||
"url-join": "^2.0.5"
|
||||
}
|
||||
},
|
||||
"ee-first": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz",
|
||||
@ -27501,7 +27374,8 @@
|
||||
"eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw=="
|
||||
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||
"dev": true
|
||||
},
|
||||
"events": {
|
||||
"version": "3.2.0",
|
||||
@ -28010,7 +27884,8 @@
|
||||
"follow-redirects": {
|
||||
"version": "1.13.0",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.0.tgz",
|
||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA=="
|
||||
"integrity": "sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA==",
|
||||
"dev": true
|
||||
},
|
||||
"for-in": {
|
||||
"version": "1.0.2",
|
||||
@ -28587,6 +28462,7 @@
|
||||
"version": "1.18.1",
|
||||
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"eventemitter3": "^4.0.0",
|
||||
"follow-redirects": "^1.0.0",
|
||||
@ -28734,23 +28610,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"http-server": {
|
||||
"version": "0.12.3",
|
||||
"resolved": "https://registry.npmjs.org/http-server/-/http-server-0.12.3.tgz",
|
||||
"integrity": "sha512-be0dKG6pni92bRjq0kvExtj/NrrAd28/8fCXkaI/4piTwQMSDSLMhWyW0NI1V+DBI3aa1HMlQu46/HjVLfmugA==",
|
||||
"requires": {
|
||||
"basic-auth": "^1.0.3",
|
||||
"colors": "^1.4.0",
|
||||
"corser": "^2.0.1",
|
||||
"ecstatic": "^3.3.2",
|
||||
"http-proxy": "^1.18.0",
|
||||
"minimist": "^1.2.5",
|
||||
"opener": "^1.5.1",
|
||||
"portfinder": "^1.0.25",
|
||||
"secure-compare": "3.0.1",
|
||||
"union": "~0.5.0"
|
||||
}
|
||||
},
|
||||
"http-signature": {
|
||||
"version": "1.2.0",
|
||||
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
|
||||
@ -32094,11 +31953,6 @@
|
||||
"is-wsl": "^2.1.1"
|
||||
}
|
||||
},
|
||||
"opener": {
|
||||
"version": "1.5.2",
|
||||
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
|
||||
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A=="
|
||||
},
|
||||
"openpgp": {
|
||||
"version": "4.10.10",
|
||||
"resolved": "https://registry.npmjs.org/openpgp/-/openpgp-4.10.10.tgz",
|
||||
@ -32724,6 +32578,7 @@
|
||||
"version": "1.0.28",
|
||||
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz",
|
||||
"integrity": "sha512-Se+2isanIcEqf2XMHjyUKskczxbPH7dQnlMjXX6+dybayyHvAf/TCgyMRlzf/B6QDhAEFOGes0pzRo3by4AbMA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"async": "^2.6.2",
|
||||
"debug": "^3.1.1",
|
||||
@ -32734,6 +32589,7 @@
|
||||
"version": "3.2.6",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
|
||||
"integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"ms": "^2.1.1"
|
||||
}
|
||||
@ -34244,7 +34100,8 @@
|
||||
"requires-port": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8="
|
||||
"integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=",
|
||||
"dev": true
|
||||
},
|
||||
"resolve": {
|
||||
"version": "1.18.1",
|
||||
@ -34587,11 +34444,6 @@
|
||||
"node-gyp-build": "^4.2.0"
|
||||
}
|
||||
},
|
||||
"secure-compare": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/secure-compare/-/secure-compare-3.0.1.tgz",
|
||||
"integrity": "sha1-8aAymzCLIh+uN7mXTz1XjQypmeM="
|
||||
},
|
||||
"select-hose": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz",
|
||||
@ -36350,14 +36202,6 @@
|
||||
"integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==",
|
||||
"dev": true
|
||||
},
|
||||
"union": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/union/-/union-0.5.0.tgz",
|
||||
"integrity": "sha512-N6uOhuW6zO95P3Mel2I2zMsbsanvvtgn6jVqJv4vbVcz/JN0OkL9suomjQGmWtxJQXOCqUJvquc1sMeNz/IwlA==",
|
||||
"requires": {
|
||||
"qs": "^6.4.0"
|
||||
}
|
||||
},
|
||||
"union-value": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz",
|
||||
@ -36503,11 +36347,6 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"url-join": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/url-join/-/url-join-2.0.5.tgz",
|
||||
"integrity": "sha1-WvIvGMBSoACkjXuCxenC4v7tpyg="
|
||||
},
|
||||
"url-parse": {
|
||||
"version": "1.4.7",
|
||||
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.4.7.tgz",
|
||||
|
@ -13,8 +13,8 @@ const environmentVars = `import {NgxLoggerLevel} from 'ngx-logger';
|
||||
export const environment = {
|
||||
production: ${isProduction},
|
||||
bloxbergChainId: ${process.env.CIC_CHAIN_ID || 8996},
|
||||
level: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.OFF'},
|
||||
serverLogLevel: ${process.env.SERVER_LEVEL || 'NgxLoggerLevel.OFF'},
|
||||
logLevel: ${process.env.LOG_LEVEL || 'NgxLoggerLevel.ERROR'},
|
||||
serverLogLevel: ${process.env.SERVER_LOG_LEVEL || 'NgxLoggerLevel.OFF'},
|
||||
loggingUrl: '${process.env.CIC_LOGGING_URL || 'http://localhost:8000'}',
|
||||
cicMetaUrl: '${process.env.CIC_META_URL || 'https://meta.dev.grassrootseconomics.net'}',
|
||||
publicKeysUrl: '${process.env.CIC_KEYS_URL || 'http://localhost:8000/keys.asc'}',
|
||||
|
37
src/app/_helpers/export-csv.ts
Normal file
37
src/app/_helpers/export-csv.ts
Normal file
@ -0,0 +1,37 @@
|
||||
function exportCsv(arrayData: any[], filename: string, delimiter = ','): void {
|
||||
if (arrayData === undefined) { return; }
|
||||
const header = Object.keys(arrayData[0]).join(delimiter) + '\n';
|
||||
let csv = header;
|
||||
arrayData.forEach(obj => {
|
||||
let row = [];
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
row.push(obj[key]);
|
||||
}
|
||||
}
|
||||
csv += row.join(delimiter) + '\n';
|
||||
});
|
||||
|
||||
const csvData = new Blob([csv], {type: 'text/csv'});
|
||||
const csvUrl = URL.createObjectURL(csvData);
|
||||
// csvUrl = 'data:text/csv;charset=utf-8,' + encodeURI(csv);
|
||||
|
||||
let downloadLink = document.createElement('a');
|
||||
downloadLink.href = csvUrl;
|
||||
downloadLink.target = '_blank';
|
||||
downloadLink.download = filename + '.csv';
|
||||
downloadLink.style.display = 'none';
|
||||
document.body.appendChild(downloadLink);
|
||||
downloadLink.click();
|
||||
}
|
||||
|
||||
function removeSpecialChar(str: string): string {
|
||||
if (str === null || str === '') {
|
||||
return '';
|
||||
}
|
||||
return str.replace(/[^a-zA-Z0-9 ]/g, '');
|
||||
}
|
||||
|
||||
export {
|
||||
exportCsv
|
||||
};
|
@ -1,12 +1,16 @@
|
||||
import {ErrorHandler, Injectable} from '@angular/core';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
import {HttpErrorResponse} from '@angular/common/http';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class GlobalErrorHandler extends ErrorHandler {
|
||||
private sentencesForWarningLogging: string[] = [];
|
||||
|
||||
constructor(private loggingService: LoggingService) {
|
||||
constructor(
|
||||
private loggingService: LoggingService,
|
||||
private router: Router
|
||||
) {
|
||||
super();
|
||||
}
|
||||
|
||||
@ -31,15 +35,17 @@ export class GlobalErrorHandler extends ErrorHandler {
|
||||
}
|
||||
|
||||
logError(error: any): void {
|
||||
const route = this.router.url;
|
||||
if (error instanceof HttpErrorResponse) {
|
||||
this.loggingService.sendErrorLevelMessage(
|
||||
`There was an HTTP error. ${error.message}, Status code: ${(error as HttpErrorResponse).status}`, this, {error});
|
||||
`There was an HTTP error on route ${route}.\n${error.message}.\nStatus code: ${(error as HttpErrorResponse).status}`,
|
||||
this, {error});
|
||||
} else if (error instanceof TypeError) {
|
||||
this.loggingService.sendErrorLevelMessage(`There was a Type error. ${error.message}`, this, {error});
|
||||
this.loggingService.sendErrorLevelMessage(`There was a Type error on route ${route}.\n${error.message}`, this, {error});
|
||||
} else if (error instanceof Error) {
|
||||
this.loggingService.sendErrorLevelMessage(`There was a general error. ${error.message}`, this, {error});
|
||||
this.loggingService.sendErrorLevelMessage(`There was a general error on route ${route}.\n${error.message}`, this, {error});
|
||||
} else {
|
||||
this.loggingService.sendErrorLevelMessage('Nobody threw an error but something happened!', this, {error});
|
||||
this.loggingService.sendErrorLevelMessage(`Nobody threw an error but something happened on route ${route}!`, this, {error});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -4,3 +4,5 @@ export * from '@app/_helpers/mock-backend';
|
||||
export * from '@app/_helpers/array-sum';
|
||||
export * from '@app/_helpers/http-getter';
|
||||
export * from '@app/_helpers/global-error-handler';
|
||||
export * from '@app/_helpers/export-csv';
|
||||
export * from '@app/_helpers/read-csv';
|
||||
|
31
src/app/_helpers/read-csv.ts
Normal file
31
src/app/_helpers/read-csv.ts
Normal file
@ -0,0 +1,31 @@
|
||||
let objCsv = {
|
||||
size: 0,
|
||||
dataFile: []
|
||||
};
|
||||
|
||||
function readCsv(input: any): any {
|
||||
if (input.files && input.files[0]) {
|
||||
let reader = new FileReader();
|
||||
reader.readAsBinaryString(input.files[0]);
|
||||
reader.onload = event => {
|
||||
objCsv.size = event.total;
|
||||
// @ts-ignore
|
||||
objCsv.dataFile = event.target.result;
|
||||
return parseData(objCsv.dataFile);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function parseData(data: any): any {
|
||||
let csvData = [];
|
||||
const lineBreak = data.split('\n');
|
||||
lineBreak.forEach(res => {
|
||||
csvData.push(res.split(','));
|
||||
});
|
||||
console.table(csvData);
|
||||
return csvData;
|
||||
}
|
||||
|
||||
export {
|
||||
readCsv
|
||||
};
|
@ -6,26 +6,43 @@ import {
|
||||
HttpInterceptor, HttpErrorResponse
|
||||
} from '@angular/common/http';
|
||||
import {Observable, throwError} from 'rxjs';
|
||||
import {catchError} from 'rxjs/operators';
|
||||
import {ErrorDialogService} from '@app/_services';
|
||||
import {catchError, retry} from 'rxjs/operators';
|
||||
import {ErrorDialogService, LoggingService} from '@app/_services';
|
||||
import {Router} from '@angular/router';
|
||||
|
||||
@Injectable()
|
||||
export class ErrorInterceptor implements HttpInterceptor {
|
||||
|
||||
constructor(private errorDialogService: ErrorDialogService) {}
|
||||
constructor(
|
||||
private errorDialogService: ErrorDialogService,
|
||||
private loggingService: LoggingService,
|
||||
private router: Router
|
||||
) {}
|
||||
|
||||
intercept(request: HttpRequest<unknown>, next: HttpHandler): Observable<HttpEvent<unknown>> {
|
||||
return next.handle(request).pipe(catchError((err: HttpErrorResponse) => {
|
||||
if (isDevMode()) {
|
||||
this.errorDialogService.openDialog({
|
||||
message: err.error.message || err.statusText || 'Unknown Error',
|
||||
status: err.status || 0
|
||||
});
|
||||
}
|
||||
if ([401, 403].indexOf(err.status) !== -1) {
|
||||
location.reload(true);
|
||||
}
|
||||
return throwError(err);
|
||||
}));
|
||||
return next.handle(request).pipe(
|
||||
catchError((err: HttpErrorResponse) => {
|
||||
let errorMessage;
|
||||
if (err.error instanceof ErrorEvent) {
|
||||
// A client-side or network error occurred. Handle it accordingly.
|
||||
errorMessage = `An error occurred: ${err.error.message}`;
|
||||
} else {
|
||||
// The backend returned an unsuccessful response code.
|
||||
// The response body may contain clues as to what went wrong.
|
||||
errorMessage = `Backend returned code ${err.status}, body was: ${JSON.stringify(err.error)}`;
|
||||
}
|
||||
this.loggingService.sendErrorLevelMessage(errorMessage, this, {error: err});
|
||||
switch (err.status) {
|
||||
case 401: // unauthorized
|
||||
this.router.navigateByUrl('/auth').then();
|
||||
break;
|
||||
case 403: // forbidden
|
||||
location.reload(true);
|
||||
break;
|
||||
}
|
||||
// Return an observable with a user-facing error message.
|
||||
return throwError(err);
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -19,11 +19,6 @@ export class HttpConfigInterceptor implements HttpInterceptor {
|
||||
request = request.clone({headers: request.headers.set('Authorization', 'Bearer ' + token)});
|
||||
}
|
||||
|
||||
if (!request.headers.has('Content-Type')) {
|
||||
request = request.clone({headers: request.headers.set('Content-Type', 'application/json')});
|
||||
}
|
||||
|
||||
request = request.clone({headers: request.headers.set('Accept', 'application/json')});
|
||||
return next.handle(request);
|
||||
}
|
||||
}
|
||||
|
@ -78,10 +78,10 @@ export const defaultAccount: AccountDetails = {
|
||||
value: '',
|
||||
}],
|
||||
fn: [{
|
||||
value: 'GE',
|
||||
value: 'Sarafu Contract',
|
||||
}],
|
||||
n: [{
|
||||
value: ['GE'],
|
||||
value: ['Sarafu', 'Contract'],
|
||||
}],
|
||||
tel: [{
|
||||
meta: {
|
||||
|
@ -1,11 +1,12 @@
|
||||
import { KeyStore } from 'cic-client-meta';
|
||||
const openpgp = require('openpgp');
|
||||
// TODO should we put this on the mutalble key store object
|
||||
import * as openpgp from 'openpgp';
|
||||
const keyring = new openpgp.Keyring();
|
||||
|
||||
interface MutableKeyStore extends KeyStore {
|
||||
loadKeyring(): Promise<void>;
|
||||
loadKeyring(): void;
|
||||
importKeyPair(publicKey: any, privateKey: any): Promise<void>;
|
||||
importPublicKey(publicKey: any): Promise<void>;
|
||||
importPublicKey(publicKey: any): void;
|
||||
importPrivateKey(privateKey: any): Promise<void>;
|
||||
getPublicKeys(): Array<any>;
|
||||
getTrustedKeys(): Array<any>;
|
||||
@ -13,7 +14,8 @@ interface MutableKeyStore extends KeyStore {
|
||||
getEncryptKeys(): Array<any>;
|
||||
getPrivateKeys(): Array<any>;
|
||||
getPrivateKey(): any;
|
||||
isValidKey(key: any): boolean;
|
||||
isValidKey(key: any): Promise<boolean>;
|
||||
isEncryptedPrivateKey(privateKey: any): Promise<boolean>;
|
||||
getFingerprint(): string;
|
||||
getKeyId(key: any): string;
|
||||
getPrivateKeyId(): string;
|
||||
@ -33,8 +35,6 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
||||
|
||||
async loadKeyring(): Promise<void> {
|
||||
await keyring.load();
|
||||
// clear any keys already in the keychain
|
||||
// keyring.clear();
|
||||
await keyring.store();
|
||||
}
|
||||
|
||||
@ -43,8 +43,8 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
||||
await keyring.privateKeys.importKey(privateKey);
|
||||
}
|
||||
|
||||
async importPublicKey(publicKey: any): Promise<void> {
|
||||
await keyring.publicKeys.importKey(publicKey);
|
||||
importPublicKey(publicKey: any): void {
|
||||
keyring.publicKeys.importKey(publicKey);
|
||||
}
|
||||
|
||||
async importPrivateKey(privateKey: any): Promise<void> {
|
||||
@ -72,15 +72,30 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
||||
}
|
||||
|
||||
getPrivateKey(): any {
|
||||
return keyring.privateKeys.keys[0];
|
||||
return keyring.privateKeys && keyring.privateKeys.keys[0];
|
||||
}
|
||||
|
||||
isValidKey(key): boolean {
|
||||
return typeof key === openpgp.Key;
|
||||
async isValidKey(key): Promise<boolean> {
|
||||
// There is supposed to be an opengpg.readKey() method but I can't find it?
|
||||
const _key = await openpgp.key.readArmored(key);
|
||||
return !_key.err;
|
||||
}
|
||||
|
||||
async isEncryptedPrivateKey(privateKey: any): Promise<boolean> {
|
||||
const imported = await openpgp.key.readArmored(privateKey);
|
||||
for (let i = 0; i < imported.keys.length; i++) {
|
||||
const key = imported.keys[i];
|
||||
if (key.isDecrypted()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
getFingerprint(): string {
|
||||
return keyring.privateKeys.keys[0].keyPacket.fingerprint;
|
||||
// TODO Handle multiple keys
|
||||
return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].keyPacket &&
|
||||
keyring.privateKeys.keys[0].keyPacket.fingerprint;
|
||||
}
|
||||
|
||||
getKeyId(key: any): string {
|
||||
@ -88,7 +103,8 @@ class MutablePgpKeyStore implements MutableKeyStore{
|
||||
}
|
||||
|
||||
getPrivateKeyId(): string {
|
||||
return keyring.privateKeys.keys[0].getKeyId().toHex();
|
||||
// TODO is there a library that comes with angular for doing this?
|
||||
return keyring.privateKeys && keyring.privateKeys.keys[0] && keyring.privateKeys.keys[0].getKeyId().toHex();
|
||||
}
|
||||
|
||||
getKeysForId(keyId: string): Array<any> {
|
||||
|
@ -3,10 +3,10 @@ import { hobaParseChallengeHeader } from '@src/assets/js/hoba.js';
|
||||
import { signChallenge } from '@src/assets/js/hoba-pgp.js';
|
||||
import {environment} from '@src/environments/environment';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
|
||||
import {MutableKeyStore, MutablePgpKeyStore} from '@app/_pgp';
|
||||
import {ErrorDialogService} from '@app/_services/error-dialog.service';
|
||||
import {first} from 'rxjs/operators';
|
||||
import { HttpClient } from '@angular/common/http';
|
||||
import {Observable } from 'rxjs';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -18,10 +18,11 @@ export class AuthService {
|
||||
mutableKeyStore: MutableKeyStore = new MutablePgpKeyStore();
|
||||
|
||||
constructor(
|
||||
private httpWrapperService: HttpWrapperService,
|
||||
private httpClient: HttpClient,
|
||||
private loggingService: LoggingService,
|
||||
private errorDialogService: ErrorDialogService
|
||||
) {
|
||||
// TODO setting these together shoulds be atomic
|
||||
if (sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'))) {
|
||||
this.sessionToken = sessionStorage.getItem(btoa('CICADA_SESSION_TOKEN'));
|
||||
}
|
||||
@ -46,7 +47,7 @@ export class AuthService {
|
||||
throw new Error('login rejected');
|
||||
}
|
||||
this.sessionLoginCount++;
|
||||
this.setState('Click button to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
|
||||
this.setState('Click button to log in');
|
||||
return;
|
||||
});
|
||||
xhr.send();
|
||||
@ -66,7 +67,7 @@ export class AuthService {
|
||||
this.sessionToken = xhr.getResponseHeader('Token');
|
||||
sessionStorage.setItem(btoa('CICADA_SESSION_TOKEN'), this.sessionToken);
|
||||
this.sessionLoginCount++;
|
||||
this.setState('Click button to perform login ' + this.sessionLoginCount + ' with token ' + this.sessionToken);
|
||||
this.setState('Click button to log in');
|
||||
return;
|
||||
});
|
||||
xhr.send();
|
||||
@ -107,8 +108,12 @@ export class AuthService {
|
||||
|
||||
|
||||
async loginResponse(o): Promise<any> {
|
||||
const r = await signChallenge(o.challenge, o.realm, environment.cicMetaUrl, this.mutableKeyStore);
|
||||
this.sendResponse(r);
|
||||
try {
|
||||
const r = await signChallenge(o.challenge, o.realm, environment.cicMetaUrl, this.mutableKeyStore);
|
||||
this.sendResponse(r);
|
||||
} catch (error) {
|
||||
this.errorDialogService.openDialog({message: 'Incorrect key passphrase.'});
|
||||
}
|
||||
}
|
||||
|
||||
loginView(): void {
|
||||
@ -119,13 +124,20 @@ export class AuthService {
|
||||
|
||||
async setKey(privateKeyArmored): Promise<boolean> {
|
||||
try {
|
||||
await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
||||
const isValidKeyCheck = await this.mutableKeyStore.isValidKey(privateKeyArmored);
|
||||
if (!isValidKeyCheck) {
|
||||
throw Error('The private key is invalid');
|
||||
}
|
||||
const isEncryptedKeyCheck = await this.mutableKeyStore.isEncryptedPrivateKey(privateKeyArmored);
|
||||
if (!isEncryptedKeyCheck) {
|
||||
throw Error('The private key doesn\'t have a password!');
|
||||
}
|
||||
const key = await this.mutableKeyStore.importPrivateKey(privateKeyArmored);
|
||||
localStorage.setItem(btoa('CICADA_PRIVATE_KEY'), privateKeyArmored);
|
||||
} catch (err) {
|
||||
this.loggingService.sendErrorLevelMessage('Failed setting key', this, {error: err});
|
||||
this.loggingService.sendErrorLevelMessage(`Failed to set key: ${err.message || err.statusText}`, this, {error: err});
|
||||
this.errorDialogService.openDialog({
|
||||
message: `Failed to set key, Enter your private key again. Reason: ${err.error.message || err.statusText}`,
|
||||
status: err.status
|
||||
message: `Failed to set key: ${err.message || err.statusText}`,
|
||||
});
|
||||
return false;
|
||||
}
|
||||
@ -135,6 +147,7 @@ export class AuthService {
|
||||
|
||||
logout(): void {
|
||||
sessionStorage.removeItem(btoa('CICADA_SESSION_TOKEN'));
|
||||
this.sessionToken = undefined;
|
||||
window.location.reload(true);
|
||||
}
|
||||
|
||||
@ -144,12 +157,11 @@ export class AuthService {
|
||||
return trustedUsers;
|
||||
}
|
||||
|
||||
async getPublicKeys(): Promise<void> {
|
||||
this.httpWrapperService.get(`${environment.publicKeysUrl}`).pipe(first()).subscribe(async res => {
|
||||
await this.mutableKeyStore.importPublicKey(res.body);
|
||||
}, error => {
|
||||
this.loggingService.sendErrorLevelMessage('There was an error fetching public keys!', this, {error});
|
||||
});
|
||||
getPublicKeys(): Observable<any> {
|
||||
return this.httpClient.get(`${environment.publicKeysUrl}`, {responseType: 'text'});
|
||||
}
|
||||
|
||||
async getPrivateKeys(): Promise<void> {
|
||||
if (this.privateKey !== undefined) {
|
||||
await this.mutableKeyStore.importPrivateKey(this.privateKey);
|
||||
}
|
||||
|
@ -59,11 +59,11 @@ export class BlockSyncService {
|
||||
});
|
||||
if (address === null) {
|
||||
this.transactionService.getAllTransactions(offset, limit).pipe(first()).subscribe(res => {
|
||||
this.fetcher(settings, res.body);
|
||||
this.fetcher(settings, res);
|
||||
});
|
||||
} else {
|
||||
this.transactionService.getAddressTransactions(address, offset, limit).pipe(first()).subscribe(res => {
|
||||
this.fetcher(settings, res.body);
|
||||
this.fetcher(settings, res);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {MatDialog} from '@angular/material/dialog';
|
||||
import {ErrorDialogComponent} from '@app/shared/error-dialog/error-dialog.component';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -11,7 +10,6 @@ export class ErrorDialogService {
|
||||
|
||||
constructor(
|
||||
public dialog: MatDialog,
|
||||
private loggingService: LoggingService
|
||||
) { }
|
||||
|
||||
openDialog(data): any {
|
||||
@ -24,10 +22,6 @@ export class ErrorDialogService {
|
||||
data
|
||||
});
|
||||
|
||||
dialogRef.afterClosed().subscribe(result => {
|
||||
this.loggingService.sendInfoLevelMessage('The dialog was closed');
|
||||
this.isDialogOpen = false;
|
||||
const res = result;
|
||||
});
|
||||
dialogRef.afterClosed().subscribe(() => this.isDialogOpen = false);
|
||||
}
|
||||
}
|
||||
|
@ -1,16 +0,0 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { HttpWrapperService } from './http-wrapper.service';
|
||||
|
||||
describe('HttpWrapperService', () => {
|
||||
let service: HttpWrapperService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(HttpWrapperService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
@ -1,60 +0,0 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import {HttpClient, HttpRequest} from '@angular/common/http';
|
||||
import {Observable} from 'rxjs';
|
||||
import * as moment from 'moment';
|
||||
import { Moment } from 'moment';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class HttpWrapperService {
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private loggingService: LoggingService,
|
||||
) { }
|
||||
|
||||
get(url: string, options?: any): Observable<Response> {
|
||||
return this.request('GET', url, null, options);
|
||||
}
|
||||
|
||||
post(url: string, body: any, options?: any): Observable<Response> {
|
||||
return this.request('POST', url, body, options);
|
||||
}
|
||||
|
||||
put(url: string, body: any, options?: any): Observable<Response> {
|
||||
return this.request('PUT', url, body, options);
|
||||
}
|
||||
|
||||
delete(url: string, options?: any): Observable<Response> {
|
||||
return this.request('DELETE', url, null, options);
|
||||
}
|
||||
|
||||
private logTime(startMoment: Moment, url: string, method: string): void {
|
||||
const requestDuration = moment().diff(startMoment, 'milliseconds');
|
||||
this.loggingService.sendInfoLevelMessage(`HTTP ${method}, URL: ${url}, Duration: ${requestDuration} ms`);
|
||||
}
|
||||
|
||||
private request(method: string, url: string, body?: any, options?: any): Observable<any> {
|
||||
this.loggingService.sendInfoLevelMessage(`Options: ${options}`);
|
||||
return Observable.create((observer: any) => {
|
||||
const requestBeginTime = moment();
|
||||
this.http.request(new HttpRequest(method, url, body, options)).subscribe((response) => {
|
||||
this.loggingService.sendInfoLevelMessage(response);
|
||||
this.logTime(requestBeginTime, `${url}`, method);
|
||||
observer.next(response);
|
||||
observer.complete();
|
||||
}, (error) => {
|
||||
switch (error.status) {
|
||||
case 403:
|
||||
observer.complete();
|
||||
break;
|
||||
default:
|
||||
observer.error(error);
|
||||
break;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
@ -5,5 +5,4 @@ export * from '@app/_services/token.service';
|
||||
export * from '@app/_services/block-sync.service';
|
||||
export * from '@app/_services/location.service';
|
||||
export * from '@app/_services/logging.service';
|
||||
export * from '@app/_services/http-wrapper.service';
|
||||
export * from '@app/_services/error-dialog.service';
|
||||
|
@ -2,7 +2,7 @@ import { Injectable } from '@angular/core';
|
||||
import {BehaviorSubject} from 'rxjs';
|
||||
import {environment} from '@src/environments/environment';
|
||||
import {first} from 'rxjs/operators';
|
||||
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -13,10 +13,10 @@ export class LocationService {
|
||||
locationsSubject = this.locationsList.asObservable();
|
||||
|
||||
constructor(
|
||||
private httpWrapperService: HttpWrapperService,
|
||||
private httpClient: HttpClient,
|
||||
) { }
|
||||
|
||||
getLocations(): void {
|
||||
this.httpWrapperService.get(`${environment.cicCacheUrl}/locations`).pipe(first()).subscribe(res => this.locationsList.next(res.body));
|
||||
this.httpClient.get(`${environment.cicCacheUrl}/locations`).pipe(first()).subscribe(res => this.locationsList.next(res));
|
||||
}
|
||||
}
|
||||
|
@ -4,8 +4,8 @@ import {BehaviorSubject, Observable} from 'rxjs';
|
||||
import {HttpGetter} from '@app/_helpers';
|
||||
import {CICRegistry} from 'cic-client';
|
||||
import Web3 from 'web3';
|
||||
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
|
||||
import {Registry, TokenRegistry} from '@app/_eth';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
@ -20,7 +20,7 @@ export class TokenService {
|
||||
tokensSubject = this.tokensList.asObservable();
|
||||
|
||||
constructor(
|
||||
private httpWrapperService: HttpWrapperService,
|
||||
private httpClient: HttpClient,
|
||||
) { }
|
||||
|
||||
async getTokens(): Promise<any> {
|
||||
@ -30,7 +30,7 @@ export class TokenService {
|
||||
}
|
||||
|
||||
getTokenBySymbol(symbol: string): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/tokens/${symbol}`);
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/tokens/${symbol}`);
|
||||
}
|
||||
|
||||
async getTokenBalance(address: string): Promise<number> {
|
||||
|
@ -13,8 +13,8 @@ import * as secp256k1 from 'secp256k1';
|
||||
import {AuthService} from '@app/_services/auth.service';
|
||||
import {defaultAccount} from '@app/_models';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
|
||||
import {Registry} from '@app/_eth';
|
||||
import {HttpClient} from '@angular/common/http';
|
||||
const Web3 = require('web3');
|
||||
const vCard = require('vcard-parser');
|
||||
|
||||
@ -30,18 +30,18 @@ export class TransactionService {
|
||||
registry = new Registry(environment.registryAddress);
|
||||
|
||||
constructor(
|
||||
private httpWrapperService: HttpWrapperService,
|
||||
private httpClient: HttpClient,
|
||||
private authService: AuthService,
|
||||
private userService: UserService,
|
||||
private loggingService: LoggingService
|
||||
) { }
|
||||
|
||||
getAllTransactions(offset: number, limit: number): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`);
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/tx/${offset}/${limit}`);
|
||||
}
|
||||
|
||||
getAddressTransactions(address: string, offset: number, limit: number): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/tx/${address}/${offset}/${limit}`);
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/tx/${address}/${offset}/${limit}`);
|
||||
}
|
||||
|
||||
async setTransaction(transaction, cacheSize: number): Promise<void> {
|
||||
|
@ -6,7 +6,6 @@ import {first} from 'rxjs/operators';
|
||||
import {ArgPair, Envelope, Syncable, User} from 'cic-client-meta';
|
||||
import {MetaResponse} from '@app/_models';
|
||||
import {LoggingService} from '@app/_services/logging.service';
|
||||
import {HttpWrapperService} from '@app/_services/http-wrapper.service';
|
||||
import {TokenService} from '@app/_services/token.service';
|
||||
import {AccountIndex, Registry} from '@app/_eth';
|
||||
import {MutableKeyStore, MutablePgpKeyStore, PGPSigner, Signer} from '@app/_pgp';
|
||||
@ -35,8 +34,7 @@ export class UserService {
|
||||
staffSubject = this.staffList.asObservable();
|
||||
|
||||
constructor(
|
||||
private http: HttpClient,
|
||||
private httpWrapperService: HttpWrapperService,
|
||||
private httpClient: HttpClient,
|
||||
private loggingService: LoggingService,
|
||||
private tokenService: TokenService
|
||||
) {
|
||||
@ -44,16 +42,16 @@ export class UserService {
|
||||
|
||||
resetPin(phone: string): Observable<any> {
|
||||
const params = new HttpParams().set('phoneNumber', phone);
|
||||
return this.httpWrapperService.get(`${environment.cicUssdUrl}/pin`, {params});
|
||||
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
|
||||
}
|
||||
|
||||
getAccountStatus(phone: string): any {
|
||||
const params = new HttpParams().set('phoneNumber', phone);
|
||||
return this.httpWrapperService.get(`${environment.cicUssdUrl}/pin`, {params});
|
||||
return this.httpClient.get(`${environment.cicUssdUrl}/pin`, {params});
|
||||
}
|
||||
|
||||
getLockedAccounts(offset: number, limit: number): any {
|
||||
return this.httpWrapperService.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
|
||||
return this.httpClient.get(`${environment.cicUssdUrl}/accounts/locked/${offset}/${limit}`);
|
||||
}
|
||||
|
||||
async changeAccountInfo(address: string, name: string, phoneNumber: string, age: string, type: string, bio: string, gender: string,
|
||||
@ -75,8 +73,8 @@ export class UserService {
|
||||
accountInfo.vcard = vCard.generate(accountInfo.vcard);
|
||||
reqBody.m.data = accountInfo;
|
||||
const accountKey = await User.toKey(address);
|
||||
this.httpWrapperService.get(`${environment.cicMetaUrl}/${accountKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => {
|
||||
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res.body)).unwrap();
|
||||
this.httpClient.get(`${environment.cicMetaUrl}/${accountKey}`, { headers: this.headers }).pipe(first()).subscribe(async res => {
|
||||
const syncableAccount: Syncable = Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
let update = [];
|
||||
for (const prop in reqBody) {
|
||||
update.push(new ArgPair(prop, reqBody[prop]));
|
||||
@ -94,67 +92,47 @@ export class UserService {
|
||||
async updateMeta(syncableAccount: Syncable, accountKey: string, headers: HttpHeaders): Promise<any> {
|
||||
const envelope = await this.wrap(syncableAccount , this.signer);
|
||||
const reqBody = envelope.toJSON();
|
||||
this.httpWrapperService.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
|
||||
this.loggingService.sendInfoLevelMessage(`Response: ${res.body}`);
|
||||
this.httpClient.put(`${environment.cicMetaUrl}/${accountKey}`, reqBody , { headers }).pipe(first()).subscribe(res => {
|
||||
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
||||
});
|
||||
}
|
||||
|
||||
getAccounts(): void {
|
||||
this.httpWrapperService.get(`${environment.cicCacheUrl}/accounts`).pipe(first()).subscribe(res => this.accountsList.next(res.body));
|
||||
this.httpClient.get(`${environment.cicCacheUrl}/accounts`).pipe(first()).subscribe(res => this.accountsList.next(res));
|
||||
}
|
||||
|
||||
getAccountById(id: number): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/accounts/${id}`);
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/accounts/${id}`);
|
||||
}
|
||||
|
||||
getActions(): void {
|
||||
this.httpWrapperService.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res.body));
|
||||
this.httpClient.get(`${environment.cicCacheUrl}/actions`).pipe(first()).subscribe(res => this.actionsList.next(res));
|
||||
}
|
||||
|
||||
getActionById(id: string): any {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/actions/${id}`);
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/actions/${id}`);
|
||||
}
|
||||
|
||||
approveAction(id: string): Observable<any> {
|
||||
return this.httpWrapperService.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: true });
|
||||
return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: true });
|
||||
}
|
||||
|
||||
revokeAction(id: string): Observable<any> {
|
||||
return this.httpWrapperService.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false });
|
||||
return this.httpClient.post(`${environment.cicCacheUrl}/actions/${id}`, { approval: false });
|
||||
}
|
||||
|
||||
getHistoryByUser(id: string): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/history/${id}`);
|
||||
}
|
||||
|
||||
getStaff(): void {
|
||||
this.httpWrapperService.get(`${environment.cicCacheUrl}/staff`).pipe(first()).subscribe(res => this.staffList.next(res.body));
|
||||
}
|
||||
|
||||
getStaffById(id: string): Observable<any> {
|
||||
return this.httpWrapperService.get(`${environment.cicCacheUrl}/staff/${id}`);
|
||||
}
|
||||
|
||||
activateStaff(id: string): Observable<any> {
|
||||
return this.httpWrapperService.post(`${environment.cicCacheUrl}/staff/${id}`, {status: 'activated'});
|
||||
}
|
||||
|
||||
deactivateStaff(id: string): Observable<any> {
|
||||
return this.httpWrapperService.post(`${environment.cicCacheUrl}/staff/${id}`, {status: 'deactivated'});
|
||||
}
|
||||
|
||||
changeStaffType(id: string, type: string): Observable<any> {
|
||||
return this.httpWrapperService.post(`${environment.cicCacheUrl}/staff/${id}`, {accountType: type});
|
||||
return this.httpClient.get(`${environment.cicCacheUrl}/history/${id}`);
|
||||
}
|
||||
|
||||
getAccountDetailsFromMeta(userKey: string): Observable<any> {
|
||||
return this.http.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
|
||||
return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers });
|
||||
}
|
||||
|
||||
getUser(userKey: string): any {
|
||||
return this.httpWrapperService.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers })
|
||||
return this.httpClient.get(`${environment.cicMetaUrl}/${userKey}`, { headers: this.headers })
|
||||
.pipe(first()).subscribe(async res => {
|
||||
return Envelope.fromJSON(JSON.stringify(res.body)).unwrap();
|
||||
return Envelope.fromJSON(JSON.stringify(res)).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {ChangeDetectionStrategy, Component, HostListener, OnInit} from '@angular/core';
|
||||
import {AuthService, LoggingService, TokenService, TransactionService} from '@app/_services';
|
||||
import {AuthService, ErrorDialogService, LoggingService, TransactionService} from '@app/_services';
|
||||
import {catchError} from 'rxjs/operators';
|
||||
import {SwUpdate} from '@angular/service-worker';
|
||||
|
||||
@Component({
|
||||
@ -16,15 +17,18 @@ export class AppComponent implements OnInit {
|
||||
|
||||
constructor(
|
||||
private authService: AuthService,
|
||||
private tokenService: TokenService,
|
||||
private transactionService: TransactionService,
|
||||
private loggingService: LoggingService,
|
||||
private errorDialogService: ErrorDialogService
|
||||
private swUpdate: SwUpdate
|
||||
) {
|
||||
(async () => {
|
||||
await this.authService.mutableKeyStore.loadKeyring();
|
||||
await this.authService.getPublicKeys();
|
||||
this.loggingService.sendInfoLevelMessage(await this.tokenService.getTokens());
|
||||
this.authService.getPublicKeys()
|
||||
.pipe(catchError(async (error) => {
|
||||
this.loggingService.sendErrorLevelMessage('Unable to load trusted public keys.', this, {error});
|
||||
this.errorDialogService.openDialog({message: 'Trusted keys endpoint can\'t be reached. Please try again later.'});
|
||||
})).subscribe(this.authService.mutableKeyStore.importPublicKey);
|
||||
})();
|
||||
this.mediaQuery.addListener(this.onResize);
|
||||
this.onResize(this.mediaQuery);
|
||||
|
@ -32,7 +32,7 @@ import { ServiceWorkerModule } from '@angular/service-worker';
|
||||
SharedModule,
|
||||
MatTableModule,
|
||||
LoggerModule.forRoot({
|
||||
level: environment.level,
|
||||
level: environment.logLevel,
|
||||
serverLogLevel: environment.serverLogLevel,
|
||||
serverLoggingUrl: `${environment.loggingUrl}/api/logs/`,
|
||||
disableConsoleLogging: false
|
||||
|
@ -29,8 +29,7 @@ export class AuthComponent implements OnInit {
|
||||
if (this.authService.privateKey !== undefined) {
|
||||
const setKey = await this.authService.setKey(this.authService.privateKey);
|
||||
if (setKey && this.authService.sessionToken !== undefined) {
|
||||
this.authService.setState(
|
||||
'Click button to perform login ' + this.authService.sessionLoginCount + ' with token ' + this.authService.sessionToken);
|
||||
this.authService.setState('Click button to log in');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@
|
||||
<ol class="breadcrumb">
|
||||
<li class="breadcrumb-item"><a routerLink="/home">Home</a></li>
|
||||
<li class="breadcrumb-item"><a routerLink="/accounts">Accounts</a></li>
|
||||
<!-- <li *ngIf="account" class="breadcrumb-item active" aria-current="page">{{account?.vcard?.fn[0].value}}</li>-->
|
||||
<li *ngIf="account !== undefined" class="breadcrumb-item active" aria-current="page">{{account?.vcard?.fn[0].value}}</li>
|
||||
</ol>
|
||||
</nav>
|
||||
<div *ngIf="!account" class="text-center">
|
||||
@ -33,13 +33,11 @@
|
||||
<h3>
|
||||
<strong> {{account?.vcard?.fn[0].value}} </strong>
|
||||
</h3>
|
||||
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance}} SRF</span>
|
||||
<span class="ml-auto"><strong>Balance:</strong> {{account?.balance | tokenRatio}} SRF</span>
|
||||
<span class="ml-2"><strong>Created:</strong> {{account?.date_registered | date}}</span>
|
||||
<span class="ml-2"><strong>Address:</strong><a href="{{bloxbergLink}}" target="_blank"> {{account?.identities.evm['bloxberg:8996']}} </a></span>
|
||||
</div>
|
||||
</div>
|
||||
<app-disbursement *ngIf="isDisbursing" (cancelDisbursmentEvent)="addTransfer()" [account]="account">
|
||||
</app-disbursement>
|
||||
<div *ngIf="account" class="card mt-3 mb-3">
|
||||
<div class="card-body">
|
||||
<form [formGroup]="accountInfoForm" (ngSubmit)="saveInfo()">
|
||||
@ -217,22 +215,22 @@
|
||||
<thead class="thead-dark">
|
||||
<tr>
|
||||
<th scope="col">NAME</th>
|
||||
<th scope="col">ACCOUNT TYPE</th>
|
||||
<th scope="col">BALANCE</th>
|
||||
<th scope="col">CREATED</th>
|
||||
<th scope="col">STATUS</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{account?.name}}</td>
|
||||
<td>{{account?.type}}</td>
|
||||
<td>{{account?.created}}</td>
|
||||
<td>{{account?.vcard?.fn[0].value}}</td>
|
||||
<td>{{account?.balance | tokenRatio}}</td>
|
||||
<td>{{account?.date_registered | date}}</td>
|
||||
<td>
|
||||
<span *ngIf="account?.status === 'active'" class="badge badge-success badge-pill">
|
||||
{{account?.status}}
|
||||
<span *ngIf="accountStatus === 'active'" class="badge badge-success badge-pill">
|
||||
{{accountStatus}}
|
||||
</span>
|
||||
<span *ngIf="account?.status === 'blocked'" class="badge badge-danger badge-pill">
|
||||
{{account?.status}}
|
||||
<span *ngIf="accountStatus === 'blocked'" class="badge badge-danger badge-pill">
|
||||
{{accountStatus}}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
@ -242,50 +240,6 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card">
|
||||
<mat-card-title class="card-header">
|
||||
History
|
||||
</mat-card-title>
|
||||
<div class="card-body">
|
||||
<mat-form-field appearance="outline">
|
||||
<mat-label> Filter </mat-label>
|
||||
<input matInput type="text" (keyup)="doHistoryFilter($event.target.value)" placeholder="Filter">
|
||||
<mat-icon matSuffix>search</mat-icon>
|
||||
</mat-form-field>
|
||||
|
||||
<mat-table class="mat-elevation-z10" [dataSource]="historyDataSource" matSort #HistoryTableSort="matSort"
|
||||
matSortActive="sender" matSortDirection="asc" matSortDisableClear>
|
||||
|
||||
<ng-container matColumnDef="user">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> User </mat-header-cell>
|
||||
<mat-cell *matCellDef="let history"> {{history.userName}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="action">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Action </mat-header-cell>
|
||||
<mat-cell *matCellDef="let history"> {{history.action}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="staff">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Staff </mat-header-cell>
|
||||
<mat-cell *matCellDef="let history"> {{history.staff}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<ng-container matColumnDef="timestamp">
|
||||
<mat-header-cell *matHeaderCellDef mat-sort-header> Timestamp </mat-header-cell>
|
||||
<mat-cell *matCellDef="let history"> {{history.timestamp | date}} </mat-cell>
|
||||
</ng-container>
|
||||
|
||||
<mat-header-row *matHeaderRowDef="historyDisplayedColumns"></mat-header-row>
|
||||
<mat-row *matRowDef="let history; columns: historyDisplayedColumns" matRipple></mat-row>
|
||||
</mat-table>
|
||||
|
||||
<mat-paginator #HistoryTablePaginator="matPaginator" [pageSize]="10"
|
||||
[pageSizeOptions]="[10, 20, 50, 100]" showFirstLastButtons></mat-paginator>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<mat-tab-group dynamicHeight mat-align-tabs="start">
|
||||
<mat-tab label="Transactions">
|
||||
<app-transaction-details [transaction]="transaction"></app-transaction-details>
|
||||
@ -303,6 +257,7 @@
|
||||
<mat-option value="reclamation">RECLAMATION</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(transactions, 'transactions')"> EXPORT </button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -376,6 +331,7 @@
|
||||
<mat-option value="group">GROUPACCOUNT</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv(accounts, 'accounts')"> EXPORT </button>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
|
@ -6,7 +6,7 @@ import {BlockSyncService, LocationService, LoggingService, TokenService, Transac
|
||||
import {ActivatedRoute, Params, Router} from '@angular/router';
|
||||
import {first} from 'rxjs/operators';
|
||||
import {FormBuilder, FormGroup, Validators} from '@angular/forms';
|
||||
import {CustomErrorStateMatcher} from '@app/_helpers';
|
||||
import {CustomErrorStateMatcher, exportCsv} from '@app/_helpers';
|
||||
import {Envelope, User} from 'cic-client-meta';
|
||||
const vCard = require('vcard-parser');
|
||||
|
||||
@ -31,21 +31,14 @@ export class AccountDetailsComponent implements OnInit {
|
||||
@ViewChild('UserTablePaginator', {static: true}) userTablePaginator: MatPaginator;
|
||||
@ViewChild('UserTableSort', {static: true}) userTableSort: MatSort;
|
||||
|
||||
historyDataSource: MatTableDataSource<any>;
|
||||
historyDisplayedColumns = ['user', 'action', 'staff', 'timestamp'];
|
||||
@ViewChild('HistoryTablePaginator', {static: true}) historyTablePaginator: MatPaginator;
|
||||
@ViewChild('HistoryTableSort', {static: true}) historyTableSort: MatSort;
|
||||
|
||||
accountInfoForm: FormGroup;
|
||||
account: any;
|
||||
accountAddress: string;
|
||||
accountBalance: number;
|
||||
accountStatus: any;
|
||||
metaAccount: any;
|
||||
accounts: any[] = [];
|
||||
accountsType = 'all';
|
||||
date: string;
|
||||
time: number;
|
||||
isDisbursing = false;
|
||||
locations: any;
|
||||
transaction: any;
|
||||
transactions: any[];
|
||||
@ -86,6 +79,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.loggingService.sendInfoLevelMessage(this.account);
|
||||
this.accountBalance = await this.tokenService.getTokenBalance(this.accountAddress);
|
||||
this.account.vcard = vCard.parse(atob(this.account.vcard));
|
||||
this.userService.getAccountStatus(this.account.vcard?.tel[0].value).pipe(first()).subscribe(response => this.accountStatus = response);
|
||||
this.accountInfoForm.patchValue({
|
||||
name: this.account.vcard?.fn[0].value,
|
||||
phoneNumber: this.account.vcard?.tel[0].value,
|
||||
@ -99,11 +93,6 @@ export class AccountDetailsComponent implements OnInit {
|
||||
locationType: this.account.location.area_type,
|
||||
});
|
||||
});
|
||||
this.userService.getHistoryByUser(this.accountAddress).pipe(first()).subscribe(response => {
|
||||
this.historyDataSource = new MatTableDataSource<any>(response.body);
|
||||
this.historyDataSource.paginator = this.historyTablePaginator;
|
||||
this.historyDataSource.sort = this.historyTableSort;
|
||||
});
|
||||
this.blockSyncService.blockSync(this.accountAddress);
|
||||
});
|
||||
this.userService.getAccounts();
|
||||
@ -127,13 +116,6 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.transactionsDataSource.sort = this.transactionTableSort;
|
||||
this.transactions = transactions;
|
||||
});
|
||||
|
||||
const d = new Date();
|
||||
this.date = `${d.getDate()}/${d.getMonth()}/${d.getFullYear()}`;
|
||||
}
|
||||
|
||||
addTransfer(): void {
|
||||
this.isDisbursing = !this.isDisbursing;
|
||||
}
|
||||
|
||||
doTransactionFilter(value: string): void {
|
||||
@ -144,10 +126,6 @@ export class AccountDetailsComponent implements OnInit {
|
||||
this.userDataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
doHistoryFilter(value: string): void {
|
||||
this.historyDataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
viewTransaction(transaction): void {
|
||||
this.transaction = transaction;
|
||||
}
|
||||
@ -160,7 +138,7 @@ export class AccountDetailsComponent implements OnInit {
|
||||
|
||||
async saveInfo(): Promise<void> {
|
||||
this.submitted = true;
|
||||
if (this.accountInfoForm.invalid) { return; }
|
||||
if (this.accountInfoForm.invalid || !confirm('Change user\'s profile information?')) { return; }
|
||||
const accountKey = await this.userService.changeAccountInfo(
|
||||
this.account.address,
|
||||
this.accountInfoFormStub.name.value,
|
||||
@ -202,12 +180,17 @@ export class AccountDetailsComponent implements OnInit {
|
||||
}
|
||||
|
||||
resetPin(): void {
|
||||
if (!confirm('Reset user\'s pin?')) { return; }
|
||||
this.userService.resetPin(this.account.phone).pipe(first()).subscribe(res => {
|
||||
this.loggingService.sendInfoLevelMessage(`Response: ${res.body}`);
|
||||
this.loggingService.sendInfoLevelMessage(`Response: ${res}`);
|
||||
});
|
||||
}
|
||||
|
||||
public trackByName(index, item): string {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
downloadCsv(data: any, filename: string): void {
|
||||
exportCsv(data, filename);
|
||||
}
|
||||
}
|
||||
|
@ -33,7 +33,7 @@
|
||||
<mat-option value="group">GROUPACCOUNT</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" routerLink="/accounts/export" type="button" class="btn btn-outline-primary ml-auto"> EXPORT </button>
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
|
@ -4,6 +4,7 @@ import {MatPaginator} from '@angular/material/paginator';
|
||||
import {MatSort} from '@angular/material/sort';
|
||||
import {LoggingService, UserService} from '@app/_services';
|
||||
import {Router} from '@angular/router';
|
||||
import {exportCsv} from '@app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-accounts',
|
||||
@ -63,4 +64,16 @@ export class AccountsComponent implements OnInit {
|
||||
this.dataSource.data = this.accounts.filter(account => account.type === this.accountsType);
|
||||
}
|
||||
}
|
||||
|
||||
refreshPaginator(): void {
|
||||
if (!this.dataSource.paginator) {
|
||||
this.dataSource.paginator = this.paginator;
|
||||
}
|
||||
|
||||
this.paginator._changePageSize(this.paginator.pageSize);
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
exportCsv(this.accounts, 'accounts');
|
||||
}
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ export class CreateAccountComponent implements OnInit {
|
||||
|
||||
onSubmit(): void {
|
||||
this.submitted = true;
|
||||
if (this.createForm.invalid) { return; }
|
||||
if (this.createForm.invalid || !confirm('Create account?')) { return; }
|
||||
this.submitted = false;
|
||||
}
|
||||
|
||||
|
@ -33,7 +33,7 @@ export class DisbursementComponent implements OnInit {
|
||||
|
||||
async createTransfer(): Promise<void> {
|
||||
this.submitted = true;
|
||||
if (this.disbursementForm.invalid) { return; }
|
||||
if (this.disbursementForm.invalid || !confirm('Make transfer?')) { return; }
|
||||
if (this.disbursementFormStub.transactionType.value === 'transfer') {
|
||||
await this.transactionService.transferRequest(
|
||||
this.account.token,
|
||||
|
@ -28,7 +28,7 @@ export class ExportAccountsComponent implements OnInit {
|
||||
|
||||
export(): void {
|
||||
this.submitted = true;
|
||||
if (this.exportForm.invalid) { return; }
|
||||
if (this.exportForm.invalid || !confirm('Export accounts?')) { return; }
|
||||
this.submitted = false;
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,10 @@
|
||||
</nav>
|
||||
<div class="card">
|
||||
<mat-card-title class="card-header">
|
||||
Actions
|
||||
<div class="row">
|
||||
Actions
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div class="card-body">
|
||||
|
||||
@ -66,7 +69,7 @@
|
||||
<mat-header-cell *matHeaderCellDef> APPROVE </mat-header-cell>
|
||||
<mat-cell *matCellDef="let action">
|
||||
<button mat-raised-button color="primary" *ngIf="!action.approval" class="btn btn-outline-success" (click)="approveAction(action)"> Approve </button>
|
||||
<button mat-raised-button color="warn" *ngIf="action.approval" class="btn btn-outline-danger" (click)="revertAction(action)"> Revert </button>
|
||||
<button mat-raised-button color="warn" *ngIf="action.approval" class="btn btn-outline-danger" (click)="disapproveAction(action)"> Disapprove </button>
|
||||
</mat-cell>
|
||||
</ng-container>
|
||||
|
||||
|
@ -5,6 +5,7 @@ import {MatSort} from '@angular/material/sort';
|
||||
import {LoggingService, UserService} from '@app/_services';
|
||||
import {animate, state, style, transition, trigger} from '@angular/animations';
|
||||
import {first} from 'rxjs/operators';
|
||||
import {exportCsv} from '@app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-admin',
|
||||
@ -23,6 +24,7 @@ export class AdminComponent implements OnInit {
|
||||
dataSource: MatTableDataSource<any>;
|
||||
displayedColumns = ['expand', 'user', 'role', 'action', 'status', 'approve'];
|
||||
action: any;
|
||||
actions: any;
|
||||
|
||||
@ViewChild(MatPaginator) paginator: MatPaginator;
|
||||
@ViewChild(MatSort) sort: MatSort;
|
||||
@ -36,6 +38,8 @@ export class AdminComponent implements OnInit {
|
||||
this.dataSource = new MatTableDataSource<any>(actions);
|
||||
this.dataSource.paginator = this.paginator;
|
||||
this.dataSource.sort = this.sort;
|
||||
this.actions = actions;
|
||||
console.log(this.actions);
|
||||
});
|
||||
}
|
||||
|
||||
@ -51,16 +55,22 @@ export class AdminComponent implements OnInit {
|
||||
}
|
||||
|
||||
approveAction(action: any): void {
|
||||
this.userService.approveAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res.body));
|
||||
if (!confirm('Approve action?')) { return; }
|
||||
this.userService.approveAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
|
||||
this.userService.getActions();
|
||||
}
|
||||
|
||||
revertAction(action: any): void {
|
||||
this.userService.revokeAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res.body));
|
||||
disapproveAction(action: any): void {
|
||||
if (!confirm('Disapprove action?')) { return; }
|
||||
this.userService.revokeAction(action.id).pipe(first()).subscribe(res => this.loggingService.sendInfoLevelMessage(res));
|
||||
this.userService.getActions();
|
||||
}
|
||||
|
||||
expandCollapse(row): void {
|
||||
row.isExpanded = !row.isExpanded;
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
exportCsv(this.actions, 'actions');
|
||||
}
|
||||
}
|
||||
|
@ -28,7 +28,7 @@ export class InviteComponent implements OnInit {
|
||||
|
||||
invite(): void {
|
||||
this.submitted = true;
|
||||
if (this.inviteForm.invalid) { return; }
|
||||
if (this.inviteForm.invalid || !confirm('Invite user?')) { return; }
|
||||
this.submitted = false;
|
||||
}
|
||||
}
|
||||
|
@ -29,7 +29,7 @@ export class OrganizationComponent implements OnInit {
|
||||
|
||||
onSubmit(): void {
|
||||
this.submitted = true;
|
||||
if (this.organizationForm.invalid) { return; }
|
||||
if (this.organizationForm.invalid || !confirm('Set organization information?')) { return; }
|
||||
this.submitted = false;
|
||||
}
|
||||
}
|
||||
|
@ -50,7 +50,7 @@
|
||||
</div>
|
||||
<hr>
|
||||
<div class="card-body">
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary"> LOGOUT ADMIN </button>
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary" (click)="logout()"> LOGOUT ADMIN </button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -59,6 +59,7 @@
|
||||
<mat-card-title class="card-header">
|
||||
<div class="row">
|
||||
TRUSTED USERS
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div class="card-body">
|
||||
|
@ -4,6 +4,7 @@ import {MatPaginator} from '@angular/material/paginator';
|
||||
import {MatSort} from '@angular/material/sort';
|
||||
import {AuthService} from '@app/_services';
|
||||
import {Staff} from '@app/_models/staff';
|
||||
import {exportCsv} from '@app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-settings',
|
||||
@ -36,4 +37,12 @@ export class SettingsComponent implements OnInit {
|
||||
doFilter(value: string): void {
|
||||
this.dataSource.filter = value.trim().toLocaleLowerCase();
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
exportCsv(this.trustedUsers, 'users');
|
||||
}
|
||||
|
||||
logout(): void {
|
||||
this.authService.logout();
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export class TokenDetailsComponent implements OnInit {
|
||||
) {
|
||||
this.route.paramMap.subscribe((params: Params) => {
|
||||
this.tokenService.getTokenBySymbol(params.get('id')).pipe(first()).subscribe(res => {
|
||||
this.token = res.body;
|
||||
this.token = res;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -18,7 +18,10 @@
|
||||
</nav>
|
||||
<div class="card">
|
||||
<mat-card-title class="card-header">
|
||||
Tokens
|
||||
<div class="row">
|
||||
Tokens
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||
</div>
|
||||
</mat-card-title>
|
||||
<div class="card-body">
|
||||
<mat-form-field appearance="outline">
|
||||
|
@ -4,6 +4,7 @@ import {MatSort} from '@angular/material/sort';
|
||||
import {LoggingService, TokenService} from '@app/_services';
|
||||
import {MatTableDataSource} from '@angular/material/table';
|
||||
import {Router} from '@angular/router';
|
||||
import {exportCsv} from '@app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-tokens',
|
||||
@ -41,4 +42,8 @@ export class TokensComponent implements OnInit {
|
||||
async viewToken(token): Promise<void> {
|
||||
await this.router.navigateByUrl(`/tokens/${token.symbol}`);
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
exportCsv(this.tokens, 'tokens');
|
||||
}
|
||||
}
|
||||
|
@ -117,7 +117,7 @@
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-success">Resend SMS</button>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<button mat-raised-button color="warn" type="button" class="btn btn-outline-danger ml-2">Reverse Transaction</button>
|
||||
<button mat-raised-button color="warn" type="button" class="btn btn-outline-danger ml-2" (click)="reverseTransaction()">Reverse Transaction</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,5 +1,6 @@
|
||||
import {ChangeDetectionStrategy, Component, Input, OnInit} from '@angular/core';
|
||||
import {Router} from '@angular/router';
|
||||
import {TransactionService} from '@app/_services';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transaction-details',
|
||||
@ -12,7 +13,10 @@ export class TransactionDetailsComponent implements OnInit {
|
||||
senderBloxbergLink: string;
|
||||
recipientBloxbergLink: string;
|
||||
|
||||
constructor(private router: Router) { }
|
||||
constructor(
|
||||
private router: Router,
|
||||
private transactionService: TransactionService
|
||||
) { }
|
||||
|
||||
ngOnInit(): void {
|
||||
this.senderBloxbergLink = 'https://blockexplorer.bloxberg.org/address/' + this.transaction?.from + '/transactions';
|
||||
@ -26,4 +30,13 @@ export class TransactionDetailsComponent implements OnInit {
|
||||
async viewRecipient(): Promise<void> {
|
||||
await this.router.navigateByUrl(`/accounts/${this.transaction.to}`);
|
||||
}
|
||||
|
||||
async reverseTransaction(): Promise<void> {
|
||||
await this.transactionService.transferRequest(
|
||||
this.transaction.token.address,
|
||||
this.transaction.to,
|
||||
this.transaction.from,
|
||||
this.transaction.value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -36,6 +36,7 @@
|
||||
<mat-option value="reclamation">RECLAMATION</mat-option>
|
||||
</mat-select>
|
||||
</mat-form-field>
|
||||
<button mat-raised-button color="primary" type="button" class="btn btn-outline-primary ml-auto mr-2" (click)="downloadCsv()"> EXPORT </button>
|
||||
</div>
|
||||
|
||||
<mat-form-field appearance="outline">
|
||||
|
@ -3,6 +3,7 @@ import {BlockSyncService, TransactionService} from '@app/_services';
|
||||
import {MatTableDataSource} from '@angular/material/table';
|
||||
import {MatPaginator} from '@angular/material/paginator';
|
||||
import {MatSort} from '@angular/material/sort';
|
||||
import {exportCsv} from '@app/_helpers';
|
||||
|
||||
@Component({
|
||||
selector: 'app-transactions',
|
||||
@ -61,4 +62,8 @@ export class TransactionsComponent implements OnInit, AfterViewInit {
|
||||
this.transactionDataSource.paginator = this.paginator;
|
||||
this.transactionDataSource.sort = this.sort;
|
||||
}
|
||||
|
||||
downloadCsv(): void {
|
||||
exportCsv(this.transactions, 'transactions');
|
||||
}
|
||||
}
|
||||
|
@ -3,8 +3,8 @@
|
||||
<p>
|
||||
Message: {{ data.message }}
|
||||
</p>
|
||||
<p>
|
||||
Status: {{ data.status }}
|
||||
<p *ngIf="data.status">
|
||||
Status: {{ data?.status }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -17,9 +17,8 @@
|
||||
</head>
|
||||
<body class="mat-typography">
|
||||
<app-root></app-root>
|
||||
<script src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||
<script async="" src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
|
||||
<script async="" src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
|
||||
<noscript>Please enable JavaScript to continue using this application.</noscript>
|
||||
<script async src="https://code.jquery.com/jquery-3.5.1.slim.min.js" integrity="sha384-DfXdz2htPH0lsSSs5nCTpuj/zy4C+OGpamoFVy38MVBnE+IbbVYUew+OrCXaRkfj" crossorigin="anonymous"></script>
|
||||
<script async src="https://cdn.jsdelivr.net/npm/popper.js@1.16.1/dist/umd/popper.min.js" integrity="sha384-9/reFTGAW83EW2RDu2S0VKaIzap3H66lZH81PoYlFhbGU+6BZp6G7niu735Sk7lN" crossorigin="anonymous"></script>
|
||||
<script async src="https://cdn.jsdelivr.net/npm/bootstrap@4.5.3/dist/js/bootstrap.min.js" integrity="sha384-w1Q4orYjBQndcko6MimVbzY0tgp4pWB4lZ7lr30WKz0vr/aWKhXdBNmNb5D92v7s" crossorigin="anonymous"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
Loading…
Reference in New Issue
Block a user