Merge branch 'master' into spencer/pwa

# Conflicts:
#	package-lock.json
#	src/app/app.component.ts
#	src/index.html
This commit is contained in:
Spencer Ofwiti 2021-03-31 09:54:35 +03:00
commit d914f6b2b8
46 changed files with 358 additions and 503 deletions

View File

@ -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
View File

@ -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",

View File

@ -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'}',

View 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
};

View File

@ -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});
}
}

View File

@ -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';

View 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
};

View File

@ -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);
})
);
}
}

View File

@ -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);
}
}

View File

@ -78,10 +78,10 @@ export const defaultAccount: AccountDetails = {
value: '',
}],
fn: [{
value: 'GE',
value: 'Sarafu Contract',
}],
n: [{
value: ['GE'],
value: ['Sarafu', 'Contract'],
}],
tel: [{
meta: {

View File

@ -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> {

View File

@ -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);
}

View File

@ -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);
});
}
}

View File

@ -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);
}
}

View File

@ -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();
});
});

View File

@ -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;
}
});
});
}
}

View File

@ -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';

View File

@ -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));
}
}

View File

@ -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> {

View File

@ -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> {

View File

@ -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();
});
}

View File

@ -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);

View File

@ -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

View File

@ -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');
}
}
}

View File

@ -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">

View File

@ -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);
}
}

View File

@ -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">

View File

@ -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');
}
}

View File

@ -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;
}

View File

@ -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,

View File

@ -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;
}
}

View File

@ -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>

View File

@ -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');
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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">

View File

@ -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();
}
}

View File

@ -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;
});
});
}

View File

@ -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">

View File

@ -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');
}
}

View File

@ -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>

View File

@ -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
);
}
}

View File

@ -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">

View File

@ -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');
}
}

View File

@ -3,8 +3,8 @@
<p>
Message: {{ data.message }}
</p>
<p>
Status: {{ data.status }}
<p *ngIf="data.status">
Status: {{ data?.status }}
</p>
</div>
</div>

View File

@ -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>