Browse Source

new version of the maintenance tool

use-env-var-docker
kenshin-samourai 4 years ago
parent
commit
f49f45fb53
  1. 1
      .gitignore
  2. 17
      .vscode/launch.json
  3. 0
      restart-example.sh
  4. 4
      static/admin/conf/index-mainnet.js
  5. 4
      static/admin/conf/index-testnet.js
  6. 587
      static/admin/css/bootstrap-theme.css
  7. 6757
      static/admin/css/bootstrap.css
  8. 695
      static/admin/css/style.css
  9. 147
      static/admin/dmt/addresses-tools/addresses-tools.html
  10. 228
      static/admin/dmt/addresses-tools/addresses-tools.js
  11. 22
      static/admin/dmt/blocks-rescan/blocks-rescan.html
  12. 45
      static/admin/dmt/blocks-rescan/blocks-rescan.js
  13. 152
      static/admin/dmt/index.html
  14. 116
      static/admin/dmt/index.js
  15. 7
      static/admin/dmt/msg-box/msg-box.html
  16. 33
      static/admin/dmt/pairing/pairing.html
  17. 65
      static/admin/dmt/pairing/pairing.js
  18. 44
      static/admin/dmt/pushtx/pushtx.html
  19. 90
      static/admin/dmt/pushtx/pushtx.js
  20. 92
      static/admin/dmt/status/status.html
  21. 68
      static/admin/dmt/status/status.js
  22. 101
      static/admin/dmt/txs-tools/txs-tools.html
  23. 117
      static/admin/dmt/txs-tools/txs-tools.js
  24. 42
      static/admin/dmt/welcome/welcome.html
  25. 177
      static/admin/dmt/xpubs-tools/xpubs-tools.html
  26. 249
      static/admin/dmt/xpubs-tools/xpubs-tools.js
  27. BIN
      static/admin/icons/samourai-logo-loading.png
  28. 17
      static/admin/index.html
  29. 46
      static/admin/index.js
  30. 136
      static/admin/lib/api-wrapper.js
  31. 64
      static/admin/lib/auth-utils.js
  32. 2377
      static/admin/lib/bootstrap.js
  33. 120
      static/admin/lib/common-script.js
  34. 39
      static/admin/lib/format-utils.js
  35. 4
      static/admin/lib/jquery-3.2.1.min.js
  36. 2
      static/admin/lib/jquery-3.5.1.min.js
  37. 34
      static/admin/lib/messages.js
  38. 132
      static/admin/tool/index.html
  39. 295
      static/admin/tool/index.js

1
.gitignore

@ -11,3 +11,4 @@ private-tests/
static/admin/conf/index.js
static/admin-legacy/
*.log
static/admin-legacy

17
.vscode/launch.json

@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${workspaceFolder}/accounts/index.js"
}
]
}

0
restart-example.sh

4
static/admin/conf/index-mainnet.js

@ -1,4 +1,4 @@
var conf = {
const conf = {
// Admin tool
adminTool: {
@ -22,4 +22,4 @@ var conf = {
statusPushtx: 'status'
}
};
}

4
static/admin/conf/index-testnet.js

@ -1,4 +1,4 @@
var conf = {
const conf = {
// Admin tool
adminTool: {
@ -22,4 +22,4 @@ var conf = {
statusPushtx: 'status'
}
};
}

587
static/admin/css/bootstrap-theme.css

@ -1,587 +0,0 @@
/*!
* Bootstrap v3.3.7 (http://getbootstrap.com)
* Copyright 2011-2016 Twitter, Inc.
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
*/
.btn-default,
.btn-primary,
.btn-success,
.btn-info,
.btn-warning,
.btn-danger {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 1px rgba(0, 0, 0, .075);
}
.btn-default:active,
.btn-primary:active,
.btn-success:active,
.btn-info:active,
.btn-warning:active,
.btn-danger:active,
.btn-default.active,
.btn-primary.active,
.btn-success.active,
.btn-info.active,
.btn-warning.active,
.btn-danger.active {
-webkit-box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
box-shadow: inset 0 3px 5px rgba(0, 0, 0, .125);
}
.btn-default.disabled,
.btn-primary.disabled,
.btn-success.disabled,
.btn-info.disabled,
.btn-warning.disabled,
.btn-danger.disabled,
.btn-default[disabled],
.btn-primary[disabled],
.btn-success[disabled],
.btn-info[disabled],
.btn-warning[disabled],
.btn-danger[disabled],
fieldset[disabled] .btn-default,
fieldset[disabled] .btn-primary,
fieldset[disabled] .btn-success,
fieldset[disabled] .btn-info,
fieldset[disabled] .btn-warning,
fieldset[disabled] .btn-danger {
-webkit-box-shadow: none;
box-shadow: none;
}
.btn-default .badge,
.btn-primary .badge,
.btn-success .badge,
.btn-info .badge,
.btn-warning .badge,
.btn-danger .badge {
text-shadow: none;
}
.btn:active,
.btn.active {
background-image: none;
}
.btn-default {
text-shadow: 0 1px 0 #fff;
background-image: -webkit-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -o-linear-gradient(top, #fff 0%, #e0e0e0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#e0e0e0));
background-image: linear-gradient(to bottom, #fff 0%, #e0e0e0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe0e0e0', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #dbdbdb;
border-color: #ccc;
}
.btn-default:hover,
.btn-default:focus {
background-color: #e0e0e0;
background-position: 0 -15px;
}
.btn-default:active,
.btn-default.active {
background-color: #e0e0e0;
border-color: #dbdbdb;
}
.btn-default.disabled,
.btn-default[disabled],
fieldset[disabled] .btn-default,
.btn-default.disabled:hover,
.btn-default[disabled]:hover,
fieldset[disabled] .btn-default:hover,
.btn-default.disabled:focus,
.btn-default[disabled]:focus,
fieldset[disabled] .btn-default:focus,
.btn-default.disabled.focus,
.btn-default[disabled].focus,
fieldset[disabled] .btn-default.focus,
.btn-default.disabled:active,
.btn-default[disabled]:active,
fieldset[disabled] .btn-default:active,
.btn-default.disabled.active,
.btn-default[disabled].active,
fieldset[disabled] .btn-default.active {
background-color: #e0e0e0;
background-image: none;
}
.btn-primary {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #265a88 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#265a88));
background-image: linear-gradient(to bottom, #337ab7 0%, #265a88 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff265a88', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #245580;
}
.btn-primary:hover,
.btn-primary:focus {
background-color: #265a88;
background-position: 0 -15px;
}
.btn-primary:active,
.btn-primary.active {
background-color: #265a88;
border-color: #245580;
}
.btn-primary.disabled,
.btn-primary[disabled],
fieldset[disabled] .btn-primary,
.btn-primary.disabled:hover,
.btn-primary[disabled]:hover,
fieldset[disabled] .btn-primary:hover,
.btn-primary.disabled:focus,
.btn-primary[disabled]:focus,
fieldset[disabled] .btn-primary:focus,
.btn-primary.disabled.focus,
.btn-primary[disabled].focus,
fieldset[disabled] .btn-primary.focus,
.btn-primary.disabled:active,
.btn-primary[disabled]:active,
fieldset[disabled] .btn-primary:active,
.btn-primary.disabled.active,
.btn-primary[disabled].active,
fieldset[disabled] .btn-primary.active {
background-color: #265a88;
background-image: none;
}
.btn-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #419641 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#419641));
background-image: linear-gradient(to bottom, #5cb85c 0%, #419641 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff419641', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #3e8f3e;
}
.btn-success:hover,
.btn-success:focus {
background-color: #419641;
background-position: 0 -15px;
}
.btn-success:active,
.btn-success.active {
background-color: #419641;
border-color: #3e8f3e;
}
.btn-success.disabled,
.btn-success[disabled],
fieldset[disabled] .btn-success,
.btn-success.disabled:hover,
.btn-success[disabled]:hover,
fieldset[disabled] .btn-success:hover,
.btn-success.disabled:focus,
.btn-success[disabled]:focus,
fieldset[disabled] .btn-success:focus,
.btn-success.disabled.focus,
.btn-success[disabled].focus,
fieldset[disabled] .btn-success.focus,
.btn-success.disabled:active,
.btn-success[disabled]:active,
fieldset[disabled] .btn-success:active,
.btn-success.disabled.active,
.btn-success[disabled].active,
fieldset[disabled] .btn-success.active {
background-color: #419641;
background-image: none;
}
.btn-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #2aabd2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#2aabd2));
background-image: linear-gradient(to bottom, #5bc0de 0%, #2aabd2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff2aabd2', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #28a4c9;
}
.btn-info:hover,
.btn-info:focus {
background-color: #2aabd2;
background-position: 0 -15px;
}
.btn-info:active,
.btn-info.active {
background-color: #2aabd2;
border-color: #28a4c9;
}
.btn-info.disabled,
.btn-info[disabled],
fieldset[disabled] .btn-info,
.btn-info.disabled:hover,
.btn-info[disabled]:hover,
fieldset[disabled] .btn-info:hover,
.btn-info.disabled:focus,
.btn-info[disabled]:focus,
fieldset[disabled] .btn-info:focus,
.btn-info.disabled.focus,
.btn-info[disabled].focus,
fieldset[disabled] .btn-info.focus,
.btn-info.disabled:active,
.btn-info[disabled]:active,
fieldset[disabled] .btn-info:active,
.btn-info.disabled.active,
.btn-info[disabled].active,
fieldset[disabled] .btn-info.active {
background-color: #2aabd2;
background-image: none;
}
.btn-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #eb9316 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#eb9316));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #eb9316 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffeb9316', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #e38d13;
}
.btn-warning:hover,
.btn-warning:focus {
background-color: #eb9316;
background-position: 0 -15px;
}
.btn-warning:active,
.btn-warning.active {
background-color: #eb9316;
border-color: #e38d13;
}
.btn-warning.disabled,
.btn-warning[disabled],
fieldset[disabled] .btn-warning,
.btn-warning.disabled:hover,
.btn-warning[disabled]:hover,
fieldset[disabled] .btn-warning:hover,
.btn-warning.disabled:focus,
.btn-warning[disabled]:focus,
fieldset[disabled] .btn-warning:focus,
.btn-warning.disabled.focus,
.btn-warning[disabled].focus,
fieldset[disabled] .btn-warning.focus,
.btn-warning.disabled:active,
.btn-warning[disabled]:active,
fieldset[disabled] .btn-warning:active,
.btn-warning.disabled.active,
.btn-warning[disabled].active,
fieldset[disabled] .btn-warning.active {
background-color: #eb9316;
background-image: none;
}
.btn-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c12e2a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c12e2a));
background-image: linear-gradient(to bottom, #d9534f 0%, #c12e2a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc12e2a', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-color: #b92c28;
}
.btn-danger:hover,
.btn-danger:focus {
background-color: #c12e2a;
background-position: 0 -15px;
}
.btn-danger:active,
.btn-danger.active {
background-color: #c12e2a;
border-color: #b92c28;
}
.btn-danger.disabled,
.btn-danger[disabled],
fieldset[disabled] .btn-danger,
.btn-danger.disabled:hover,
.btn-danger[disabled]:hover,
fieldset[disabled] .btn-danger:hover,
.btn-danger.disabled:focus,
.btn-danger[disabled]:focus,
fieldset[disabled] .btn-danger:focus,
.btn-danger.disabled.focus,
.btn-danger[disabled].focus,
fieldset[disabled] .btn-danger.focus,
.btn-danger.disabled:active,
.btn-danger[disabled]:active,
fieldset[disabled] .btn-danger:active,
.btn-danger.disabled.active,
.btn-danger[disabled].active,
fieldset[disabled] .btn-danger.active {
background-color: #c12e2a;
background-image: none;
}
.thumbnail,
.img-thumbnail {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.dropdown-menu > li > a:hover,
.dropdown-menu > li > a:focus {
background-color: #e8e8e8;
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.dropdown-menu > .active > a,
.dropdown-menu > .active > a:hover,
.dropdown-menu > .active > a:focus {
background-color: #2e6da4;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.navbar-default {
background-image: -webkit-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -o-linear-gradient(top, #fff 0%, #f8f8f8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fff), to(#f8f8f8));
background-image: linear-gradient(to bottom, #fff 0%, #f8f8f8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#fff8f8f8', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .15), 0 1px 5px rgba(0, 0, 0, .075);
}
.navbar-default .navbar-nav > .open > a,
.navbar-default .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -o-linear-gradient(top, #dbdbdb 0%, #e2e2e2 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dbdbdb), to(#e2e2e2));
background-image: linear-gradient(to bottom, #dbdbdb 0%, #e2e2e2 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdbdbdb', endColorstr='#ffe2e2e2', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .075);
}
.navbar-brand,
.navbar-nav > li > a {
text-shadow: 0 1px 0 rgba(255, 255, 255, .25);
}
.navbar-inverse {
background-image: -webkit-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -o-linear-gradient(top, #3c3c3c 0%, #222 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#3c3c3c), to(#222));
background-image: linear-gradient(to bottom, #3c3c3c 0%, #222 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff3c3c3c', endColorstr='#ff222222', GradientType=0);
filter: progid:DXImageTransform.Microsoft.gradient(enabled = false);
background-repeat: repeat-x;
border-radius: 4px;
}
.navbar-inverse .navbar-nav > .open > a,
.navbar-inverse .navbar-nav > .active > a {
background-image: -webkit-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -o-linear-gradient(top, #080808 0%, #0f0f0f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#080808), to(#0f0f0f));
background-image: linear-gradient(to bottom, #080808 0%, #0f0f0f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff080808', endColorstr='#ff0f0f0f', GradientType=0);
background-repeat: repeat-x;
-webkit-box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
box-shadow: inset 0 3px 9px rgba(0, 0, 0, .25);
}
.navbar-inverse .navbar-brand,
.navbar-inverse .navbar-nav > li > a {
text-shadow: 0 -1px 0 rgba(0, 0, 0, .25);
}
.navbar-static-top,
.navbar-fixed-top,
.navbar-fixed-bottom {
border-radius: 0;
}
@media (max-width: 767px) {
.navbar .navbar-nav .open .dropdown-menu > .active > a,
.navbar .navbar-nav .open .dropdown-menu > .active > a:hover,
.navbar .navbar-nav .open .dropdown-menu > .active > a:focus {
color: #fff;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
}
.alert {
text-shadow: 0 1px 0 rgba(255, 255, 255, .2);
-webkit-box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: inset 0 1px 0 rgba(255, 255, 255, .25), 0 1px 2px rgba(0, 0, 0, .05);
}
.alert-success {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #c8e5bc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#c8e5bc));
background-image: linear-gradient(to bottom, #dff0d8 0%, #c8e5bc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffc8e5bc', GradientType=0);
background-repeat: repeat-x;
border-color: #b2dba1;
}
.alert-info {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #b9def0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#b9def0));
background-image: linear-gradient(to bottom, #d9edf7 0%, #b9def0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffb9def0', GradientType=0);
background-repeat: repeat-x;
border-color: #9acfea;
}
.alert-warning {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #f8efc0 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#f8efc0));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #f8efc0 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fff8efc0', GradientType=0);
background-repeat: repeat-x;
border-color: #f5e79e;
}
.alert-danger {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #e7c3c3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#e7c3c3));
background-image: linear-gradient(to bottom, #f2dede 0%, #e7c3c3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffe7c3c3', GradientType=0);
background-repeat: repeat-x;
border-color: #dca7a7;
}
.progress {
background-image: -webkit-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #ebebeb 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#f5f5f5));
background-image: linear-gradient(to bottom, #ebebeb 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffebebeb', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #286090 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#286090));
background-image: linear-gradient(to bottom, #337ab7 0%, #286090 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff286090', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-success {
background-image: -webkit-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -o-linear-gradient(top, #5cb85c 0%, #449d44 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5cb85c), to(#449d44));
background-image: linear-gradient(to bottom, #5cb85c 0%, #449d44 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5cb85c', endColorstr='#ff449d44', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-info {
background-image: -webkit-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -o-linear-gradient(top, #5bc0de 0%, #31b0d5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#5bc0de), to(#31b0d5));
background-image: linear-gradient(to bottom, #5bc0de 0%, #31b0d5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff5bc0de', endColorstr='#ff31b0d5', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-warning {
background-image: -webkit-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -o-linear-gradient(top, #f0ad4e 0%, #ec971f 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f0ad4e), to(#ec971f));
background-image: linear-gradient(to bottom, #f0ad4e 0%, #ec971f 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff0ad4e', endColorstr='#ffec971f', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-danger {
background-image: -webkit-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -o-linear-gradient(top, #d9534f 0%, #c9302c 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9534f), to(#c9302c));
background-image: linear-gradient(to bottom, #d9534f 0%, #c9302c 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9534f', endColorstr='#ffc9302c', GradientType=0);
background-repeat: repeat-x;
}
.progress-bar-striped {
background-image: -webkit-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: -o-linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
background-image: linear-gradient(45deg, rgba(255, 255, 255, .15) 25%, transparent 25%, transparent 50%, rgba(255, 255, 255, .15) 50%, rgba(255, 255, 255, .15) 75%, transparent 75%, transparent);
}
.list-group {
border-radius: 4px;
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
box-shadow: 0 1px 2px rgba(0, 0, 0, .075);
}
.list-group-item.active,
.list-group-item.active:hover,
.list-group-item.active:focus {
text-shadow: 0 -1px 0 #286090;
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2b669a 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2b669a));
background-image: linear-gradient(to bottom, #337ab7 0%, #2b669a 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2b669a', GradientType=0);
background-repeat: repeat-x;
border-color: #2b669a;
}
.list-group-item.active .badge,
.list-group-item.active:hover .badge,
.list-group-item.active:focus .badge {
text-shadow: none;
}
.panel {
-webkit-box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
box-shadow: 0 1px 2px rgba(0, 0, 0, .05);
}
.panel-default > .panel-heading {
background-image: -webkit-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -o-linear-gradient(top, #f5f5f5 0%, #e8e8e8 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f5f5f5), to(#e8e8e8));
background-image: linear-gradient(to bottom, #f5f5f5 0%, #e8e8e8 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff5f5f5', endColorstr='#ffe8e8e8', GradientType=0);
background-repeat: repeat-x;
}
.panel-primary > .panel-heading {
background-image: -webkit-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -o-linear-gradient(top, #337ab7 0%, #2e6da4 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#337ab7), to(#2e6da4));
background-image: linear-gradient(to bottom, #337ab7 0%, #2e6da4 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff337ab7', endColorstr='#ff2e6da4', GradientType=0);
background-repeat: repeat-x;
}
.panel-success > .panel-heading {
background-image: -webkit-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -o-linear-gradient(top, #dff0d8 0%, #d0e9c6 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#dff0d8), to(#d0e9c6));
background-image: linear-gradient(to bottom, #dff0d8 0%, #d0e9c6 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffdff0d8', endColorstr='#ffd0e9c6', GradientType=0);
background-repeat: repeat-x;
}
.panel-info > .panel-heading {
background-image: -webkit-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -o-linear-gradient(top, #d9edf7 0%, #c4e3f3 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#d9edf7), to(#c4e3f3));
background-image: linear-gradient(to bottom, #d9edf7 0%, #c4e3f3 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffd9edf7', endColorstr='#ffc4e3f3', GradientType=0);
background-repeat: repeat-x;
}
.panel-warning > .panel-heading {
background-image: -webkit-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -o-linear-gradient(top, #fcf8e3 0%, #faf2cc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#fcf8e3), to(#faf2cc));
background-image: linear-gradient(to bottom, #fcf8e3 0%, #faf2cc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fffcf8e3', endColorstr='#fffaf2cc', GradientType=0);
background-repeat: repeat-x;
}
.panel-danger > .panel-heading {
background-image: -webkit-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -o-linear-gradient(top, #f2dede 0%, #ebcccc 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#f2dede), to(#ebcccc));
background-image: linear-gradient(to bottom, #f2dede 0%, #ebcccc 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fff2dede', endColorstr='#ffebcccc', GradientType=0);
background-repeat: repeat-x;
}
.well {
background-image: -webkit-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -o-linear-gradient(top, #e8e8e8 0%, #f5f5f5 100%);
background-image: -webkit-gradient(linear, left top, left bottom, from(#e8e8e8), to(#f5f5f5));
background-image: linear-gradient(to bottom, #e8e8e8 0%, #f5f5f5 100%);
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffe8e8e8', endColorstr='#fff5f5f5', GradientType=0);
background-repeat: repeat-x;
border-color: #dcdcdc;
-webkit-box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
box-shadow: inset 0 1px 3px rgba(0, 0, 0, .05), 0 1px 0 rgba(255, 255, 255, .1);
}
/*# sourceMappingURL=bootstrap-theme.css.map */

6757
static/admin/css/bootstrap.css

File diff suppressed because it is too large

695
static/admin/css/style.css

@ -13,7 +13,6 @@ h1 {
input, select {
padding: 5px;
border: 0;
margin-bottom: 16px;
width: 100%;
border: 1px solid #bfbfbf;
background-color: #1a1d1f;
@ -36,6 +35,31 @@ a:hover {
color: #2196f3;
}
.row {
margin-left: 0;
margin-right: 0;
}
.small {
font-size: 11px;
}
.beta {
font-size: 11px;
color: #9f9f9f;
}
/* RAW TX */
pre.raw-tx {
white-space: pre-wrap;
max-width: 500px;
color: #e0e0e3;
background: #1a1d1f;
border-radius: 0;
border: 0;
}
/* ICONS */
.mini-icon {
height: 16px;
width: 16px;
@ -46,7 +70,6 @@ a:hover {
width: 32px;
}
/* ALIGNMENTS */
.left {
text-align: left!important;
@ -64,17 +87,32 @@ a:hover {
text-align: justify!important;
}
/* BOXES WIDTHS */
.halfwidth-left {
width: 49%;
min-width: 49%;
margin-left: 0;
margin-right: 0.7%;
}
.small {
font-size: 11px;
.halfwidth-right {
width: 49%;
min-width: 49%;
margin-left: 0.7%;
margin-right: 0;
}
.beta {
font-size: 11px;
color: #9f9f9f;
.fullwidth {
width: 100%;
min-width: 100%;
}
/* BUTTONS*/
.btn {
font-size: 12px;
padding: 4px 12px;
}
/* BUTTON SUCCESS */
.btn-success {
background-image: -webkit-linear-gradient(top, #2196f3 0%, #1186e3 100%);
background-image: -o-linear-gradient(top, #2196f3 0%, #1186e3 100%);
@ -121,236 +159,281 @@ fieldset[disabled] .btn-success.active {
background-image: none;
}
/* TABLES */
table.spaced tr th,
table.spaced tr td {
padding: 5px;
}
/* SEARCH BOX */
.search-input {
width: 500px;
td.table-label {
font-weight: bold;
padding-top: 2px;
padding-bottom: 2px;
vertical-align: top;
}
.search-btn-icon {
background-image: url('/icons/ic_search_white_24dp_2x.png');
background-clip: padding-box;
background-size: cover;
width: 28px;
height: 28px;
vertical-align: middle;
margin: 0!important;
color: transparent;
background-color: transparent;
border-color: transparent;
box-shadow: none;
webkit-box-shadow: none;
text-shadow: none;
webkit-text-shadow: none;
td.table-value {
padding-left: 15px;
padding-top: 2px;
padding-bottom: 2px;
vertical-align: top;
}
.search-btn-icon:hover,
.search-btn-icon:focus {
outline: 0;
/* BOXES */
.two-columns-left {
vertical-align: top;
display: inline-block;
padding: 0;
background-color: transparent;
width: 49%;
min-width: 49%;
margin-left: 0;
margin-right: 0.7%;
}
table.spaced tr th,
table.spaced tr td {
padding: 5px;
.two-columns-right {
vertical-align: top;
display: inline-block;
padding: 0;
background-color: transparent;
width: 49%;
min-width: 49%;
margin-left: 0.7%;
margin-right: 0;
}
.two-columns-left .box,
.two-columns-right .box {
margin-bottom: 15px;
}
/* COMMON PAGES */
.container #welcome-msg {
text-align: center;
.box {
display: inline-block;
padding: 10px 10px 10px 10px;
background-color: rgba(255, 255, 255, 0.1);
}
.container h3 {
margin-bottom: 20px;
margin-top: 0;
.box-header {
width: 100%;
font-size: 12px;
font-weight: bold;
}
.container span {
display: block;
margin-left: auto;
margin-right: auto;
.box-body {
width: 100%;
}
.container span.label-field {
.box-context {
width: 100%;
font-size: 12px;
margin-bottom: 2px;
padding-left: 2px;
font-style: italic;
margin: 0 10px 10px 0;
}
.container button {
display: inline-block;
margin-left: 8px;
margin-right: 8px;
margin-bottom: 8px;
.box-main {
margin: 20px 0;
}
.container #welcome-msg {
margin-bottom: 60px;
padding-bottom: 10px;
#box-msg {
text-align: center;
position: fixed;
bottom: 0;
right: 0;
left: 0;
z-index: 100;
}
.container #welcome-msg h1 {
color: #e0e0e3;
/* MESSAGE BOX */
.msg, .msg-error, .msg-info {
color: #505050;
font-weight: bold;
padding: 0;
}
.container div.box-content {
color: #e0e0e3;
.msg {
background: #81b6e2;
}
.msg-error {
background: #ca7c7c;
}
.msg-info {
background: #8caf8c;
}
/* PAGES - COMMONS */
body.dmt {
min-height: 100vh;
background-image: url("../icons/samourai-logo-loading.png");
background-repeat: no-repeat;
background-position: center;
}
#body {
padding: 0;
color: #efefef;
}
#body,
#form {
padding-top: 20px;
}
#body #main > div {
min-height: 80vh;
background-color: #1a1d1f;
}
#body #main .title {
margin: 0;
background-color: rgba(255, 255, 255, 0.1);
text-align: left;
padding: 30px;
}
.container div.box-content-transp {
color: #e0e0e3;
background: transparent;
text-align: left;
padding: 30px;
#body #main h1 {
font-size: 24px;
margin: 0 0 20px 0;
padding: 0;
}
.container div.title-section {
color: #e0e0e3;
background: transparent;
text-align: left;
h3 {
margin-bottom: 20px;
border-bottom: 1px solid #bfbfbf;
margin-top: 0;
}
.container div.box-actions {
margin-top: 10px;
text-align: center;
span {
display: block;
margin-left: auto;
margin-right: auto;
}
.container .optional-actions {
margin-top: 30px;
text-align: center;
font-size: 11px;
button {
display: inline-block;
margin-left: 8px;
margin-right: 8px;
}
.container .optional-actions a {
margin: 0 5px;
.box-content {
color: #e0e0e3;
background-color: rgba(255, 255, 255, 0.1);
text-align: left;
padding: 30px;
}
.box-actions {
margin-top: 10px;
text-align: center;
}
.container #body,
.container #form {
padding-top: 20px;
background-color: rgba(255, 255, 255, 0.1);
.amount-sent {
color: #f77c7c;
}
.container #body {
border-bottom: 1px solid #bfbfbf;
.amount-received {
color: #76d776;
}
/* Navigation tab menu */
.container #tab-menu div {
/* NAVIGATION MENU*/
#body #menu {
padding-left: 0;
padding-right: 0;
}
.container .nav-pills {
/*border-bottom: 1px solid #bfbfbf;*/
#body #menu .title {
margin: 0;
background-color: rgba(255, 255, 255, 0.1);
}
#body #menu .title h1 {
font-size: 16px;
margin: 0 0 5px 0;
padding: 5px;
}
.nav-pills {
color: #e0e0e3;
display: flex;
overflow: hidden;
}
.container .nav-pills > li {
.nav-pills > li {
padding-left: 0;
padding-right: 0;
border: none;
}
.container .nav-pills > li > a {
.nav-pills > li > a {
color: #cfd8dc;
border-radius: 0;
margin-left: 0;
border: none;
margin-left: 5px;
text-decoration: none;
cursor: pointer;
padding: 6px;
padding: 6px 4px;
}
.container .nav-pills > li > a:hover {
.nav-pills > li > a:hover {
color: #fff;
background-color: transparent;
background-color: transparent!important;
}
.container .nav-pills > li.active > a,
.container .nav-pills > li.active > a:hover {
.nav-pills > li.active > a,
.nav-pills > li.active > a:hover {
color: #fff;
border-top: 1px solid #fff;
background-color: rgba(255, 255, 255, 0.1);
outline: none;
background-color: transparent!important;
font-weight: 600;
text-decoration: none;
cursor: default;
}
/* HEADER */
.container #header {
#header {
height: 60px;
border-bottom-width: 3px;
border-bottom-color: #b0bec5;
border-bottom-style: solid;
display: flex;
display: -ms-flexbox;
align-items: center;
-ms-flex-align: center;
}
.container #header .title {
color: #e0e0e3;
margin-left: 0!important;
#header div {
padding-left: 0;
padding-right: 0;
}
.container #header .login-box {
text-align: right;
#header span {
display:inline;
}
.container #header .login-box a,
.container #header .login-box .login,
.container #header .login-box .wallet-blc {
#header .title {
color: #e0e0e3;
font-size: 12px;
margin-left: 0!important;
}
.container #header .login-box a,
.container #header .login-box .login {
display: inline-block;
#header .login-box {
text-align: right;
}
.container #header .login-box a {
vertical-align: middle;
#header .login-box a {
color: #e0e0e3;
font-size: 12px;
display: inline-block;
vertical-align: middle;
}
.container #header span {
display:inline;
/* PAGES - HOME */
#login-page {
padding: 100px 0;
}
/* MESSAGES */
.container div.msg-boxes {
#login-page #welcome-msg {
text-align: center;
margin-top: 20px;
font-size: 14px;
margin-bottom: 60px;
padding-bottom: 10px;
}
.container div.msg-boxes .msg {
#login-page #welcome-msg h1 {
color: #e0e0e3;
}
.container div.msg-boxes .msg-error {
color: #c76464;
}
.container div.msg-boxes .msg-info {
color: #52c152;
}
/* LOGIN PAGE */
#login-page {
padding: 100px 0;
}
#login-page #signin {
margin-top: 10px;
}
@ -363,13 +446,14 @@ table.spaced tr td {
display:inline;
}
#body {
padding: 40px;
color: #efefef;
/* PAGES - STATUS */
#tor-status-ind,
#nginx-status-ind,
#nodejs-status-ind {
color: #76d776;
}
/* PAIRING */
/* PAGES - PAIRING */
#qr-label,
#qr-explorer-label {
margin: 0 0 20px 0;
@ -391,64 +475,299 @@ table.spaced tr td {
margin: auto;
}
/* FORM FIED*/
#cell-args,
#cell-args2,
#cell-args3 {
/* PAGES - BLOCKS RESCAN */
#blocks-rescan-form span {
display: inline;
}
#blocks-rescan-form .box-body {
text-align: center;
}
#blocks-rescan-form input {
width: 60px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
.halfwidth {
width: 49%;
min-width: 49%;
/* PAGES - XPUBS TOOL */
#xpubs-tool-search-form span {
display: inline;
}
.fullwidth {
#xpubs-tool-search-form .box-body {
text-align: center;
}
#xpubs-tool-search-form input {
width: 400px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#xpubs-tool-details {
width: 100%;
min-width: 100%;
}
#cell-args2,
#cell-args3 {
width: 24%;
min-width: 24%;
#xpubs-tool-header {
margin: 0 0 20px 0;
}
/* JSON DATA */
.json-data-container {
max-width: 100%;
word-wrap: break-word;
overflow: visible;
margin-top: 20px;
#xpubs-tool-actions {
text-align: center;
}
#json-data {
text-align: left;
min-height: 400px;
max-width: 945px;
outline: 1px solid #252525;
border: none;
padding: 5px;
margin: 5px;
color: lightgreen;
background-color: #252525;
#xpubs-rescans-actions span {
display: inline;
}
#xpubs-rescans-actions input {
width: 50px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#xpubs-tool-details #xpub-value {
overflow: hidden;
}
#xpubs-tool-details-row1 table {
width: 100%;
}
#xpubs-tool-details-row1 table .table-label {
width: 15%;
}
#xpubs-tool-details-row1 table .table-value {
width: 35%;
}
#xpubs-tool-details-row2 table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
#xpubs-tool-details-row2 table tbody tr:first-child {
height: 0;
}
#xpubs-tool-details-row2 table tbody tr:first-child td:first-child {
width: 80px;
}
#xpubs-tool-details-row2 tbody tr td:last-child {
overflow: hidden;
white-space: nowrap;
}
#xpubs-tool-details-row2 table a {
font-weight: bold;
text-decoration: underline;
color: #efefef;
}
#xpubs-tool-details-row2 table .table-label {
width: 30%;
}
#xpubs-tool-details-row2 table .table-value {
width: 70%;
}
#xpubs-tool-import {
text-align: center;
}
#xpubs-tool-import span {
display: inline;
}
#xpubs-tool-import select {
width: 80px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#xpubs-tool-import #import-xpub {
font-weight: bold;
}
/* PAGES - ADDRESSES TOOL */
#addresses-tool-search-form span {
display: inline;
}
#addresses-tool-search-form .box-body {
text-align: center;
}
#addresses-tool-search-form input {
width: 280px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#addresses-tool-details {
width: 100%;
}
#json-data span {
display: inline;
max-width: 800px;
word-wrap: break-word;
overflow: visible;
#addresses-tool-header {
margin: 0 0 20px 0;
}
#addresses-tool-actions {
text-align: center;
}
#addresses-rescans-actions span {
display: inline;
}
#addresses-rescans-actions input {
width: 50px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#addresses-tool-details-row1 table,
#addresses-tool-details-row2 table {
width: 100%;
}
#addresses-tool-details-row1 table .table-label,
#addresses-tool-details-row2 table .table-label {
width: 110px;
}
#addresses-tool-details-row2 #addr-xpub {
overflow: hidden;
white-space: nowrap;
max-width: 200px;
}
#json-data .string { color: lightgreen; }
#json-data .number { color: lightgreen; }
#json-data .boolean { color: lightgreen; }
#json-data .null { color: lightgreen; }
#json-data .key { color: lightgreen; }
#json-data .info { color: lightskyblue; }
#json-data .error { color: orangered; }
#addresses-tool-details-row3 table {
width: 100%;
table-layout: fixed;
border-collapse: collapse;
}
#addresses-tool-details-row3 table tbody tr:first-child {
height: 0;
}
#addresses-tool-details-row3 table tbody tr:first-child td:first-child {
width: 80px;
}
#addresses-tool-details-row3 tbody tr td:last-child {
overflow: hidden;
white-space: nowrap;
}
#addresses-tool-details-row3 table a {
font-weight: bold;
text-decoration: underline;
color: #efefef;
}
#addresses-tool-details-row3 table .table-label {
width: 30%;
}
#addresses-tool-details-row3 table .table-value {
width: 70%;
}
#addresses-tool-import {
text-align: center;
}
#addresses-tool-import span {
display: inline;
}
#addresses-tool-import select {
width: 80px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#addresses-tool-import #import-address {
font-weight: bold;
}
/* PAGES - TRANSACTIONS TOOL */
#txs-tool-search-form span {
display: inline;
}
#txs-tool-search-form .box-body {
text-align: center;
}
#txs-tool-search-form input {
width: 400px;
margin-left: 5px;
margin-right: 5px;
display: inline-block;
}
#txs-tool-details {
width: 100%;
}
#txs-tool-header {
margin: 0 0 20px 0;
}
#txs-tool-actions {
text-align: center;
}
#txs-tool-details #txid-value {
overflow: hidden;
}
#txs-tool-details-row1 table {
width: 100%;
}
#txs-tool-details-row1 table .table-label {
width: 15%;
}
#txs-tool-details-row1 table .table-value {
width: 35%;
}
/* PAGES - HELP DMT */
#welcome span {
margin: 20px 0;
}
#welcome .items-category {
margin: 20px 0 10px 0;
font-weight: bold;
font-size: 16px;
}
#welcome .item {
margin: 10px 0 0 10px;
font-weight: bold;
}
#welcome .item-descr {
margin: 5px 0 10px 10px;
}
/* SPACERS */

147
static/admin/dmt/addresses-tools/addresses-tools.html

@ -0,0 +1,147 @@
<div id="addresses-tool">
<h1>ADDRESSES TOOL</h1>
<div class="box-context">Check if an address is tracked by your Dojo. Import and track a new address. Rescan the full history of an address.</div>
<div class="row box-main">
<!-- ADDRESS SEARCH FORM -->
<div id="addresses-tool-search-form" class="fullwidth box">
<div class="box-body">
<span>Check if </span>
<input id="address" type="text" placeholder="address">
<span> is tracked by your Dojo </span>
<button id="btn-address-search-go" class="btn btn-success" type="button">GO</button>
</div>
</div>
<!-- ADDRESS IMPORT -->
<div id="addresses-tool-import" class="fullwidth box">
<div class="box-body">
<div>
<span>This address isn't tracked by your Dojo.</span>
</div>
<div class="spacer20"></div>
<div>
<span>Do you want to import </span>
<span id="import-address"></span>
<span> and track its activity?</span>
<button id="btn-address-import-go" class="btn btn-success" type="button">IMPORT</button>
<button id="btn-address-import-cancel" class="btn btn-success" type="button">CANCEL</button>
</div>
</div>
</div>
<!-- ADDRESS DETAILS -->
<div id="addresses-tool-details">
<div id="addresses-tool-header" class="row box-main">
<div class="fullwidth box">
<div id="addr-value" class="box-body center"></div>
</div>
</div>
<div id="addresses-tool-actions" class="row box-main">
<div class="center">
<button id="btn-address-details-rescan" class="btn btn-success" type="button">RESCAN THIS ADDRESS</button>
<button id="btn-address-details-reset" class="btn btn-success" type="button">SEARCH ANOTHER ADDRESS</button>
</div>
</div>
<div id="addresses-rescans-actions" class="row box-main">
<div class="center">
<span>Do you want to rescan this address?</span>
<button id="btn-address-rescan-go" class="btn btn-success" type="button">RESCAN</button>
<button id="btn-address-rescan-cancel" class="btn btn-success" type="button">CANCEL</button>
</div>
</div>
<div id="addresses-tool-details-row1" class="row box-main">
<!-- GENERAL INFO -->
<div id="box-general" class="fullwidth box">
<div class="box-header">GENERAL INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Balance</td>
<td class="table-value" id="addr-balance"></td>
</tr>
<tr>
<td class="table-label">Number of Txs</td>
<td class="table-value" id="addr-nb-txs"></td>
</tr>
<tr>
<td class="table-label">Number of UTXOs</td>
<td class="table-value" id="addr-nb-utxos"></td>
</tr>
<tr>
<td class="table-label">Segwit</td>
<td class="table-value" id="addr-segwit"></td>
</tr>
<tr>
<td class="table-label">Address Type</td>
<td class="table-value" id="addr-type"></td>
</tr>
</table>
</div>
</div>
</div>
<div id="addresses-tool-details-row2" class="row box-main">
<!-- HD ADDRESS INFO -->
<div id="box-hd" class="fullwidth box">
<div class="box-header">DERIVATION INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Derivation path</td>
<td class="table-value" id="addr-deriv-path"></td>
</tr>
<tr>
<td class="table-label" colspan="2">Derived from</td>
</tr>
<tr>
<td id="addr-xpub" colspan="2"></td>
</tr>
</table>
</div>
</div>
</div>
<div id="addresses-tool-details-row3" class="row box-main">
<!-- TXS LIST -->
<div id="box-txs" class="halfwidth-left box">
<div class="box-header">MOST RECENT TRANSACTIONS</div>
<div class="spacer10"></div>
<div class="box-body">
<table id="addr-table-list-txs">
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- UTXOS LIST -->
<div id="box-utxos" class="halfwidth-right box">
<div class="box-header">UNSPENT TRANSACTION OUTPUTS</div>
<div class="spacer10"></div>
<div class="box-body">
<table id="addr-table-list-utxos">
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script include-js="addresses-tools/addresses-tools.js"></script>

228
static/admin/dmt/addresses-tools/addresses-tools.js

@ -0,0 +1,228 @@
const screenAddressesToolsScript = {
explorerInfo: null,
currentAddress: null,
initPage: function() {
this.getExplorerInfo()
// Sets the event handlers
$('#btn-address-search-go').click(() => {this.searchAddress()})
$('#btn-address-details-reset').click(() => {this.showSearchForm()})
$('#btn-address-details-rescan').click(() => {this.showRescanForm()})
$('#btn-address-rescan-go').click(() => {this.rescanAddress()})
$('#btn-address-rescan-cancel').click(() => {this.hideRescanForm()})
$('#btn-address-import-go').click(() => {this.importAddress()})
$('#btn-address-import-cancel').click(() => {this.showSearchForm()})
$('#addresses-tool').keyup(evt => {
if (evt.keyCode === 13) {
this.searchAddress()
}
})
},
preparePage: function() {
this.hideRescanForm()
this.showSearchForm()
$("#address").focus()
},
getExplorerInfo: function() {
lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
searchAddress: function() {
lib_msg.displayMessage('Search in progress...');
const address = $('#address').val()
this.currentAddress = address
return this._searchAddress(address).then(() => {
lib_msg.cleanMessagesUi()
})
},
_searchAddress: function(address) {
return lib_api.getAddressInfo(address).then(addressInfo => {
if (addressInfo && addressInfo['tracked']) {
this.setAddressDetails(addressInfo)
this.showAddressDetails()
const jsonData = {'active': address}
return lib_api.getWallet(jsonData).then(walletInfo => {
// Display the txs
const txs = walletInfo['txs']
for (let tx of txs)
this.setTxDetails(tx)
// Display the UTXOs
const utxos = walletInfo['unspent_outputs'].sort((a,b) => {
return a['confirmations'] - b['confirmations']
})
$('#addr-nb-utxos').text(utxos.length)
for (let utxo of utxos)
this.setUtxoDetails(utxo)
})
} else {
lib_msg.displayErrors('address not found')
this.showImportForm(false)
}
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
throw e
})
},
importAddress: function() {
lib_msg.displayMessage('Processing address import...');
const jsonData = {'active': this.currentAddress}
return lib_api.getWallet(jsonData)
.then(result => {
this._searchAddress(this.currentAddress).then(() => {
lib_msg.displayInfo('Import complete')
})
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
rescanAddress: function() {
lib_msg.displayMessage('Processing address rescan...');
return lib_api.getAddressRescan(this.currentAddress)
.then(result => {
this.hideRescanForm()
this._searchAddress(this.currentAddress).then(() => {
lib_msg.displayInfo('Rescan complete')
})
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
setAddressDetails: function(addressInfo) {
$('tr.tx-row').remove()
$('tr.utxo-row').remove()
$('#addr-value').text(this.currentAddress)
$('#addr-nb-txs').text(addressInfo['n_tx'])
$('#addr-nb-utxos').text('-')
const balance = parseInt(addressInfo['balance']) / 100000000
$('#addr-balance').text(`${balance} BTC`)
const addrType = (addressInfo['type'] == 'hd') ? 'Derived from an XPUB' : 'Loose address'
$('#addr-type').text(addrType)
if (addressInfo['segwit']) {
$('#addr-segwit').html('&#10003;')
$('#addr-segwit').css('color', '#76d776')
} else {
$('#addr-segwit').text('-')
$('#addr-segwit').css('color', '#f77c7c')
}
if (addressInfo['type'] == 'hd') {
$('#addr-xpub').text(addressInfo['xpub'])
$('#addr-deriv-path').text(addressInfo['path'])
$('#addresses-tool-details-row2').show()
} else {
$('#addresses-tool-details-row2').hide()
}
},
setTxDetails: function(tx) {
const txid = tx['hash']
const txidDisplay = `${txid.substring(0,50)}...`
const amount = parseInt(tx['result']) / 100000000
const amountLabel = amount < 0 ? amount : `+${amount}`
const amountStyle = amount < 0 ? 'amount-sent' : 'amount-received'
const date = lib_fmt.unixTsToLocaleString(tx['time'])
const txUrl = lib_cmn.getExplorerTxUrl(txid, this.explorerInfo)
const newRow = `<tr class="tx-row"><td colspan="2">&nbsp;</td></tr>
<tr class="tx-row">
<td class="table-label" colspan="2">
<a href="${txUrl}" target="_blank">${txidDisplay}</a>
</td>
</tr>
<tr class="tx-row">
<td class="table-label">Amount</td>
<td class="table-value ${amountStyle}">${amountLabel} BTC</td>
</tr>
<tr class="tx-row">
<td class="table-label">Block height</td>
<td class="table-value">${tx['block_height']}</td>
</tr>
<tr class="tx-row">
<td class="table-label">Date</td>
<td class="table-value">${date}</td>
</tr>`
$('#addr-table-list-txs tr:last').after(newRow)
},
setUtxoDetails: function(utxo) {
const txid = utxo['tx_hash']
const txidVout = `${txid.substring(0,50)}...:${utxo['tx_output_n']}`
const amount = parseInt(utxo['value']) / 100000000
const txUrl = lib_cmn.getExplorerTxUrl(txid, this.explorerInfo)
const newRow = `<tr class="utxo-row"><td colspan="2">&nbsp;</td></tr>
<tr class="utxo-row">
<td class="table-label" colspan="2">
<a href="${txUrl}" target="_blank">${txidVout}</a>
</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Amount</td>
<td class="table-value">${amount} BTC</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Address</td>
<td class="table-value">${utxo['addr']}</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Confirmations</td>
<td class="table-value">${utxo['confirmations']}</td>
</tr>`
$('#addr-table-list-utxos tr:last').after(newRow)
},
showSearchForm: function() {
$('#addresses-tool-details').hide()
$('#addresses-tool-import').hide()
$('#address').val('')
$('#addresses-tool-search-form').show()
lib_msg.cleanMessagesUi()
},
showImportForm: function() {
$('#addresses-tool-search-form').hide()
$('#addresses-tool-details').hide()
$('#import-address').text(this.currentAddress)
$('#addresses-tool-import').show()
},
showAddressDetails: function() {
$('#addresses-tool-search-form').hide()
$('#addresses-tool-import').hide()
$('#addresses-tool-details').show()
},
showRescanForm: function() {
$('#addresses-tool-actions').hide()
$('#addresses-rescans-actions').show()
lib_msg.cleanMessagesUi()
},
hideRescanForm: function() {
$('#addresses-rescans-actions').hide()
$('#addresses-tool-actions').show()
},
}
screenScripts.set('#screen-addresses-tools', screenAddressesToolsScript)

22
static/admin/dmt/blocks-rescan/blocks-rescan.html

@ -0,0 +1,22 @@
<div id="blocks-rescan">
<h1>BLOCKS RESCAN</h1>
<div class="box-context">Force the Tracker to rescan a range of blocks.</div>
<div class="row box-main">
<div id="blocks-rescan-form" class="box fullwidth">
<div class="box-body">
<span>Rescan blocks between</span>
<input id="rescan-from-height" type="text" placeholder="height">
<span> and </span>
<input id="rescan-to-height" type="text" placeholder="height">
<button id="btn-rescan-go"
class="btn btn-success"
type="button">GO</button>
</div>
</div>
</div>
</div>
<script include-js="blocks-rescan/blocks-rescan.js"></script>

45
static/admin/dmt/blocks-rescan/blocks-rescan.js

@ -0,0 +1,45 @@
const screenBlocksRescanScript = {
initPage: function() {
// Sets the event handlers
$('#btn-rescan-go').click(() => {
this.processRescan()
})
$('#blocks-rescan').keyup(evt => {
if (evt.keyCode === 13) {
this.processRescan()
}
})
},
preparePage: function() {
$("#rescan-from-height").focus()
},
processRescan: function() {
lib_msg.displayMessage('Processing...');
let fromHeight = $("#rescan-from-height").val()
let toHeight = $("#rescan-to-height").val()
fromHeight = parseInt(fromHeight)
toHeight = (toHeight) ? parseInt(toHeight) : fromHeight;
lib_api.getBlocksRescan(fromHeight, toHeight).then(result => {
if (!result)
return
const fromHeightRes = result['fromHeight']
const toHeightRes = result['toHeight']
const msg = `successfully rescanned blocks between height ${fromHeightRes} and height ${toHeightRes}`
lib_msg.displayInfo(msg)
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
}).then(() => {
$('#rescan-from-height').val('')
$('#rescan-to-height').val('')
})
},
}
screenScripts.set('#screen-blocks-rescan', screenBlocksRescanScript)

152
static/admin/dmt/index.html

@ -0,0 +1,152 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOJO // MAINTENANCE TOOL</title>
<link rel="stylesheet" type="text/css" href="../css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../css/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="../css/style.css">
<script src="../lib/jquery-3.5.1.min.js"></script>
<script src="../lib/jquery.qrcode.min.js"></script>
<script src="../conf/index.js"></script>
<script src="../lib/common-script.js"></script>
<script src="../lib/api-wrapper.js"></script>
<script src="../lib/auth-utils.js"></script>
<script src="../lib/format-utils.js"></script>
<script src="../lib/messages.js"></script>
<script src="index.js"></script>
</head>
<body class="dmt">
<div id="top-container" class="container" style="display: none">
<!-- HEADER -->
<div id="header" class="row">
<div class="col-xs-9">
<h1 class="title"><span>DOJO // MAINTENANCE TOOL</span> <span id="dojo-version" class="beta">beta</span></h1>
</div>
<div class="col-xs-3 login-box">
<a id="btn-logout" style="display: inline;" href="#" title="DISCONNECT">
<img src="../icons/ic_power_settings_new_white_24dp_1x.png" class="mini-icon"/>
</a>
</div>
</div>
<div class="spacer30"></div>
<!-- BODY -->
<div id="body" class="row">
<!-- MENU -->
<div id="menu" class="col-xs-2">
<div class="title">
<h1>MONITORING</h1>
</div>
<ul id="tab-menu_list" class="nav nav-pills nav-stacked">
<li id="link-welcome" style="display: none;">
<a href="#">WELCOME</a>
</li>
<li id="link-status">
<a href="#">DOJO STATUS</a>
</li>
<li id="link-pushtx">
<a href="#">PUSHTX STATUS</a>
</li>
</ul>
<div class="spacer20"></div>
<div class="title">
<h1>TOOLS</h1>
</div>
<ul id="tab-menu_list2" class="nav nav-pills nav-stacked">
<li id="link-pairing">
<a href="#">PAIRING</a>
</li>
<li id="link-xpubs-tools">
<a href="#">XPUBS TOOL</a>
</li>
<li id="link-addresses-tools">
<a href="#">ADDRESSES TOOL</a>
</li>
<li id="link-txs-tools">
<a href="#">TRANSACTIONS TOOL</a>
</li>
<li id="link-blocks-rescan">
<a href="#">BLOCKS RESCAN</a>
</li>
</ul>
<div class="spacer20"></div>
<div class="title">
<h1>HELP</h1>
</div>
<ul id="tab-menu_list3" class="nav nav-pills nav-stacked">
<li id="link-help-dmt">
<a href="#">HELP DMT</a>
</li>
<li id="link-dojo-telegram">
<a href="https://t.me/samourai_dojo" target="_blank">DOJO TELEGRAM CHAT</a>
</li>
<li id="link-wp-telegram">
<a href="https://t.me/whirlpool_trollbox" target="_blank">WHIRLPOOL TELEGRAM CHAT</a>
</li>
<li id="link-sw-support">
<a href="https://t.me/SamouraiWallet" target="_blank">SAMOURAI TELEGRAM CHAT</a>
</li>
</ul>
</div>
<div class="col-xs-1"></div>
<!-- MAIN AREA -->
<div id="main" class="col-xs-9">
<!-- WELCOME -->
<div id="screen-welcome"
include-html="welcome/welcome.html"
style="display: none">
</div>
<!-- STATUS -->
<div id="screen-status"
include-html="status/status.html"
style="display: none">
</div>
<!-- PUSH TX -->
<div id="screen-pushtx"
include-html="pushtx/pushtx.html"
style="display: none">
</div>
<!-- PAIRING -->
<div id="screen-pairing"
include-html="pairing/pairing.html"
style="display: none">
</div>
<!-- XPUBS TOOLS -->
<div id="screen-xpubs-tools"
include-html="xpubs-tools/xpubs-tools.html"
style="display: none">
</div>
<!-- ADDRESSES TOOLS -->
<div id="screen-addresses-tools"
include-html="addresses-tools/addresses-tools.html"
style="display: none">
</div>
<!-- TRANSACTIONS TOOLS -->
<div id="screen-txs-tools"
include-html="txs-tools/txs-tools.html"
style="display: none">
</div>
<!-- BLOCKS RESCAN -->
<div id="screen-blocks-rescan"
include-html="blocks-rescan/blocks-rescan.html"
style="display: none">
</div>
<!-- HELP DMT -->
<div id="screen-help-dmt"
include-html="welcome/welcome.html"
style="display: none">
</div>
</div>
</div>
</div>
<!-- MSG BOX -->
<div id="box-msg"
include-html="msg-box/msg-box.html">
</div>
</body>
</html>

116
static/admin/dmt/index.js

@ -0,0 +1,116 @@
/**
* Global obkjects
*/
// Ordered list of screens
const screens = [
'#screen-welcome',
'#screen-status',
'#screen-pushtx',
'#screen-pairing',
'#screen-xpubs-tools',
'#screen-addresses-tools',
'#screen-txs-tools',
'#screen-blocks-rescan',
'#screen-help-dmt'
]
// Ordered list of menu items
const tabs = [
'#link-welcome',
'#link-status',
'#link-pushtx',
'#link-pairing',
'#link-xpubs-tools',
'#link-addresses-tools',
'#link-txs-tools',
'#link-blocks-rescan',
'#link-help-dmt'
]
// Mapping of scripts associaed to screens
const screenScripts = new Map()
/**
* UI initialization
*/
function initTabs() {
// Activates the current tab
let currentTab = sessionStorage.getItem('activeTab')
if (!currentTab)
currentTab = '#link-status'
$(currentTab).addClass('active')
// Sets event handlers
for (let tab of tabs) {
$(tab).click(function() {
$(sessionStorage.getItem('activeTab')).removeClass('active')
sessionStorage.setItem('activeTab', tab)
$(tab).addClass('active')
preparePage()
})
}
}
function initPages() {
// Dynamic loading of screens and scripts
lib_cmn.includeHTML(_initPages)
// Dojo version
let lblVersion = sessionStorage.getItem('lblVersion')
if (lblVersion == null) {
lib_api.getPairingInfo().then(apiInfo => {
lblVersion = 'v' + apiInfo['pairing']['version'] + ' beta'
sessionStorage.setItem('lblVersion', lblVersion)
$('#dojo-version').text(lblVersion)
})
} else {
$('#dojo-version').text(lblVersion)
}
}
function _initPages() {
for (let screen of screens) {
const screenScript = screenScripts.get(screen)
if (screenScript)
screenScript.initPage()
}
preparePage()
$('#top-container').show()
}
function preparePage() {
lib_msg.cleanMessagesUi()
const activeTab = sessionStorage.getItem('activeTab')
for (let idxTab in tabs) {
const screen = screens[idxTab]
if (tabs[idxTab] == activeTab) {
$(screen).show()
if (screenScripts.has(screen))
screenScripts.get(screen).preparePage()
} else {
$(screen).hide()
}
}
}
/**
* Processing on loading completed
*/
$(document).ready(function() {
// Refresh the access token
lib_auth.refreshAccessToken()
setInterval(() => {
lib_auth.refreshAccessToken()
}, 300000)
// Inits menu and pages
initTabs()
initPages()
// Set event handlers
$('#btn-logout').click(function() {
lib_auth.logout()
})
})

7
static/admin/dmt/msg-box/msg-box.html

@ -0,0 +1,7 @@
<div class="row box-msg">
<div class="col-xs-12">
<div id="msg" class="msg"></div>
<div id="errors" class="msg-error"></div>
<div id="info" class="msg-info"></div>
</div>
</div>

33
static/admin/dmt/pairing/pairing.html

@ -0,0 +1,33 @@
<div id="pairing">
<h1>PAIRING</h1>
<div class="box-context">Pair your wallet to your Dojo and to your Block Explorer with a simple QRCode.</div>
<div class="row box-main">
<div id="dojo-pairing" class="halfwidth-left box">
<div class="box-header">DOJO</div>
<div class="spacer10"></div>
<div class="box-body" id="qr-container">
<div class="center">Scan this QRCode with your wallet</div>
<div class="spacer10"></div>
<div id="qr-pairing"></div>
<div class="spacer10"></div>
</div>
</div>
<div id="explorer-pairing" class="halfwidth-right box">
<div class="box-header">BLOCK EXPLORER</div>
<div class="spacer10"></div>
<div class="box-body" id="qr-explorer-container">
<div class="center">Scan this QRCode with your wallet</div>
<div class="spacer10"></div>
<div id="qr-explorer-pairing"></div>
<div class="spacer10"></div>
</div>
</div>
</div>
</div>
<script include-js="pairing/pairing.js"></script>

65
static/admin/dmt/pairing/pairing.js

@ -0,0 +1,65 @@
const screenPairingScript = {
initPage: function() {},
preparePage: function() {
this.displayQRPairing()
},
loadPairingPayloads: function() {
let result = {
'api': null,
'explorer': null
}
lib_msg.displayMessage('Loading pairing payloads...');
return lib_api.getPairingInfo().then(apiInfo => {
if (apiInfo) {
apiInfo['pairing']['url'] = window.location.protocol + '//' + window.location.host + conf['api']['baseUri']
result['api'] = apiInfo
}
}).then(() => {
return lib_api.getExplorerPairingInfo()
}).then(explorerInfo => {
if (explorerInfo)
result['explorer'] = explorerInfo
lib_msg.cleanMessagesUi()
return result
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
return result
})
},
displayQRPairing: function() {
this.loadPairingPayloads().then(
function (result) {
if (result) {
if (result['api']) {
const textJson = JSON.stringify(result['api'], null, 4)
$("#qr-pairing").html('') // clear qrcode first
$('#qr-pairing').qrcode({width: 256, height: 256, text: textJson})
}
if (result['explorer'] && result['explorer']['pairing']['url']) {
const textJson = JSON.stringify(result['explorer'], null, 4)
$("#qr-explorer-pairing").html('') // clear qrcode first
$('#qr-explorer-pairing').qrcode({width: 256, height: 256, text: textJson})
} else {
$("#qr-label").removeClass('halfwidth')
$("#qr-label").addClass('fullwidth')
$("#qr-container").removeClass('halfwidth')
$("#qr-container").addClass('fullwidth')
$("#qr-explorer-label").hide()
$("#qr-explorer-container").hide()
}
}
},
function (jqxhr) {}
);
}
}
screenScripts.set('#screen-pairing', screenPairingScript)

44
static/admin/dmt/pushtx/pushtx.html

@ -0,0 +1,44 @@
<div id="pushtx-status">
<h1>PUSHTX STATUS</h1>
<div class="box-context">Monitor the transactions pushed through your Dojo.</div>
<div class="row box-main">
<div id="txs-pushed" class="box fullwidth">
<div class="box-header">TRANSACTIONS PUSHED</div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Uptime</td>
<td class="table-value" id="pushed-uptime"></td>
</tr>
<tr>
<td class="table-label">Number of Transactions</td>
<td class="table-value" id="pushed-count"></td>
</tr>
<tr>
<td class="table-label">Total Amount</td>
<td class="table-value" id="pushed-amount"></td>
</tr>
</table>
</div>
</div>
</div>
<div class="row box-main">
<div id="txs-scheduled" class="box fullwidth">
<div class="box-header">TRANSACTIONS SCHEDULED</div>
<div class="box-body">
<table id="table-scheduled-txs">
<thead>
<td colspan="2"></td>
</thead>
<tbody></tbody>
</table>
</div>
</div>
</div>
</div>
<script include-js="pushtx/pushtx.js"></script>

90
static/admin/dmt/pushtx/pushtx.js

@ -0,0 +1,90 @@
const pushtxScript = {
processedSchedTxs: new Set(),
initPage: function() {
// Refresh PushTx status
setInterval(() => {this.refreshPushTxStatus()}, 60000)
// Refresh ScheduledTxs list
setInterval(() => {this.refreshScheduledTxsList()}, 60000)
},
preparePage: function() {
this.refreshPushTxStatus()
this.refreshScheduledTxsList()
},
refreshPushTxStatus: function() {
lib_msg.displayMessage('Loading PushTx status info...');
lib_api.getPushtxStatus().then(pushTxStatus => {
if (pushTxStatus) {
const data = pushTxStatus['data']
const uptime = lib_cmn.timePeriod(data['uptime'])
$('#pushed-uptime').text(uptime)
$('#pushed-count').text(data['push']['count'])
$('#pushed-amount').text(data['push']['amount'])
lib_msg.cleanMessagesUi()
}
}).catch(e => {
$('#pushed-uptime').text('-')
$('#pushed-count').text('-')
$('#pushed-amount').text('-')
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
refreshScheduledTxsList: function() {
lib_msg.displayMessage('Loading PushTx orchestrator status info...');
lib_api.getOrchestratorStatus().then(orchestrStatus => {
if(orchestrStatus) {
const data = orchestrStatus['data']
for (let tx of data['txs']) {
if (!this.processedSchedTxs.has(tx['schTxid'])) {
this.displayScheduledTx(tx)
this.processedSchedTxs.add(tx['schTxid'])
}
}
lib_msg.cleanMessagesUi()
}
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
displayScheduledTx: function(tx) {
const newRow = `<tr><td colspan="2">&nbsp;</td></tr>
<tr class="table-value">
<td class="table-label">TXID</td>
<td class="table-value" id="scheduled-txid">${tx['schTxid']}</td>
</tr>
<tr class="table-value">
<td class="table-label">Schedule Id</td>
<td class="table-value" id="scheduled-txid">${tx['schID']}</td>
</tr>
<tr class="table-value">
<td class="table-label">Scheduled for block</td>
<td class="table-value" id="scheduled-trigger">${tx['schTrigger']}</td>
</tr>
<tr class="table-value">
<td class="table-label">Created on</td>
<td class="table-value" id="scheduled-created">${lib_fmt.unixTsToLocaleString(tx['schCreated'])}</td>
</tr>
<tr class="table-value">
<td class="table-label">Parent TXID</td>
<td class="table-value" id="scheduled-parent-txid">${tx['schParentTxid']}</td>
</tr>
<tr class="table-value">
<td class="table-label">Raw Transaction</td>
<td class="table-value" id="scheduled-tx">
<pre class="raw-tx">${tx['schRaw']}</pre>
</td>
</tr>`
$('#table-scheduled-txs tr:last').after(newRow)
},
}
screenScripts.set('#screen-pushtx', pushtxScript)

92
static/admin/dmt/status/status.html

@ -0,0 +1,92 @@
<div id="status">
<h1>DOJO STATUS</h1>
<div class="box-context">Monitor the health of some core components of your Dojo.</div>
<div class="row box-main">
<div id="left-column" class="two-columns-left">
<div id="bitcoind-status" class="fullwidth box">
<div class="box-header">FULL NODE</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Status</td>
<td class="table-value" id="node-status-ind"></td>
</tr>
<tr>
<td class="table-label">Uptime</td>
<td class="table-value" id="node-uptime"></td>
</tr>
<tr>
<td class="table-label">Latest block</td>
<td class="table-value" id="node-chaintip"></td>
</tr>
<tr>
<td class="table-label">Bitcoind version</td>
<td class="table-value" id="node-version"></td>
</tr>
<tr>
<td class="table-label">Network</td>
<td class="table-value" id="node-network"></td>
</tr>
<tr>
<td class="table-label">Connected nodes</td>
<td class="table-value" id="node-conn"></td>
</tr>
<tr>
<td class="table-label">Network relay fee</td>
<td class="table-value" id="node-relay-fee"></td>
</tr>
</table>
</div>
</div>
</div>
<div id="right-column" class="two-columns-right">
<div id="tracker-status" class="fullwidth box">
<div class="box-header">TRACKER</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Status</td>
<td class="table-value" id="tracker-status-ind"></td>
</tr>
<tr>
<td class="table-label">Uptime</td>
<td class="table-value" id="tracker-uptime"></td>
</tr>
<tr>
<td class="table-label">Latest block</td>
<td class="table-value" id="tracker-chaintip"></td>
</tr>
</table>
</div>
</div>
<div id="web-status" class="fullwidth box">
<div class="box-header">WEB</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Tor status</td>
<td class="table-value" id="tor-status-ind">&#10003;</td>
</tr>
<tr>
<td class="table-label">Nginx status</td>
<td class="table-value" id="nginx-status-ind">&#10003;</td>
</tr>
<tr>
<td class="table-label">Node.js status</td>
<td class="table-value" id="nodejs-status-ind">&#10003;</td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
<script include-js="status/status.js"></script>

68
static/admin/dmt/status/status.js

@ -0,0 +1,68 @@
const statusScript = {
initPage: function() {
// Refresh API status
setInterval(() => {this.refreshApiStatus()}, 60000)
// Refresh PushTx status
setInterval(() => {this.refreshPushTxStatus()}, 60000)
},
preparePage: function() {
this.refreshApiStatus()
this.refreshPushTxStatus()
},
refreshApiStatus: function() {
lib_msg.displayMessage('Loading API status info...');
return lib_api.getApiStatus().then(apiStatus => {
if (apiStatus) {
$('#tracker-status-ind').html('&#10003;')
$('#tracker-status-ind').css('color', '#76d776')
$('#tracker-uptime').text(apiStatus['uptime'])
$('#tracker-chaintip').text(apiStatus['blocks'])
lib_msg.cleanMessagesUi()
}
}).catch(e => {
$('#tracker-status-ind').text('X')
$('#tracker-status-ind').css('color', '#f77c7c')
$('#tracker-uptime').text('-')
$('#tracker-chaintip').text('-')
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
refreshPushTxStatus: function() {
lib_msg.displayMessage('Loading Tracker status info...');
lib_api.getPushtxStatus().then(pushTxStatus => {
if (pushTxStatus) {
const data = pushTxStatus['data']
$('#node-status-ind').html('&#10003;')
$('#node-status-ind').css('color', '#76d776')
const uptime = lib_cmn.timePeriod(data['uptime'])
$('#node-uptime').text(uptime)
$('#node-chaintip').text(data['bitcoind']['blocks'])
$('#node-version').text(data['bitcoind']['version'])
const network = data['bitcoind']['testnet'] == true ? 'testnet' : 'mainnet'
$('#node-network').text(network)
$('#node-conn').text(data['bitcoind']['conn'])
$('#node-relay-fee').text(data['bitcoind']['relayfee'])
lib_msg.cleanMessagesUi()
}
}).catch(e => {
$('#node-status-ind').text('-')
$('#node-status-ind').css('color', '#f77c7c')
$('#node-uptime').text('-')
$('#node-chaintip').text('-')
$('#node-version').text('-')
$('#node-network').text('-')
$('#node-conn').text('-')
$('#node-relay-fee').text('-')
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
}
screenScripts.set('#screen-status', statusScript)

101
static/admin/dmt/txs-tools/txs-tools.html

@ -0,0 +1,101 @@
<div id="txs-tool">
<h1>TRANSACTIONS TOOL</h1>
<div class="box-context">Check if a transaction is found in a block or in the mempool of your full node.</div>
<div class="row box-main">
<!-- TRANSACTION SEARCH FORM -->
<div id="txs-tool-search-form" class="fullwidth box">
<div class="box-body">
<span>Search transaction with this </span>
<input id="txid" type="text" placeholder="TXID">
<button id="btn-tx-search-go" class="btn btn-success" type="button">GO</button>
</div>
</div>
<!-- TRANSACTION DETAILS -->
<div id="txs-tool-details">
<div id="txs-tool-header" class="row box-main">
<div class="fullwidth box">
<div class="box-body center">
<a id="txid-value" href="" target="_blank"></a>
</div>
</div>
</div>
<div id="txs-tool-actions" class="row box-main">
<div class="center">
<button id="btn-txs-details-reset" class="btn btn-success" type="button">SEARCH ANOTHER TRANSACTION</button>
</div>
</div>
<div id="txs-tool-details-row1" class="row box-main">
<!-- GENERAL INFO -->
<div id="box-general" class="halfwidth-left box">
<div class="box-header">GENERAL INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">First-seen date</td>
<td class="table-value" id="tx-firstseen"></td>
</tr>
<tr>
<td class="table-label">Found in</td>
<td class="table-value" id="tx-location"></td>
</tr>
<tr>
<td class="table-label">Amount</td>
<td class="table-value" id="tx-amount"></td>
</tr>
<tr>
<td class="table-label">Fees</td>
<td class="table-value" id="tx-fees"></td>
</tr>
<tr>
<td class="table-label">Feerate</td>
<td class="table-value" id="tx-vfeerate"></td>
</tr>
<tr>
<td class="table-label">Number of inputs</td>
<td class="table-value" id="tx-nb-inputs"></td>
</tr>
<tr>
<td class="table-label">Number of outputs</td>
<td class="table-value" id="tx-nb-outputs"></td>
</tr>
</table>
</div>
</div>
<!-- TECHNICAL INFO -->
<div id="box-technical" class="halfwidth-right box">
<div class="box-header">TECHNICAL INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Virtual size</td>
<td class="table-value" id="tx-vsize"></td>
</tr>
<tr>
<td class="table-label">Raw size</td>
<td class="table-value" id="tx-size"></td>
</tr>
<tr>
<td class="table-label">Transaction version</td>
<td class="table-value" id="tx-version"></td>
</tr>
<tr>
<td class="table-label">nLockTime</td>
<td class="table-value" id="tx-nlocktime"></td>
</tr>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script include-js="txs-tools/txs-tools.js"></script>

117
static/admin/dmt/txs-tools/txs-tools.js

@ -0,0 +1,117 @@
const screenTxsToolsScript = {
explorerInfo: null,
currentTxid: null,
initPage: function() {
this.getExplorerInfo()
// Sets the event handlers
$('#btn-tx-search-go').click(() => {this.searchTx()})
$('#btn-txs-details-reset').click(() => {this.showSearchForm()})
$('#txs-tool').keyup(evt => {
if (evt.keyCode === 13) {
this.searchTx()
}
})
},
preparePage: function() {
this.showSearchForm()
$("#txid").focus()
},
getExplorerInfo: function() {
lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
searchTx: function() {
lib_msg.displayMessage('Search in progress...');
const txid = $('#txid').val()
this.currentTxid = txid
return this._searchTx(txid).then(() => {
lib_msg.cleanMessagesUi()
})
},
_searchTx: function(txid) {
return lib_api.getTransaction(txid).then(txInfo => {
if (txInfo) {
console.log(txInfo)
this.setTxDetails(txInfo)
this.showTxDetails()
}
}).catch(e => {
lib_msg.displayErrors('No transaction found')
console.log(e)
throw e
})
},
setTxDetails: function(txInfo) {
$('tr.input-row').remove()
$('tr.output-row').remove()
const txUrl = lib_cmn.getExplorerTxUrl(this.currentTxid, this.explorerInfo)
$('#txid-value').text(this.currentTxid)
$('#txid-value').attr('href', txUrl)
const firstseen = lib_fmt.unixTsToLocaleString(txInfo['created'])
$('#tx-firstseen').text(firstseen)
if (txInfo.hasOwnProperty('block'))
$('#tx-location').text(` Block ${txInfo['block']['height']}`)
else
$('#tx-location').text(' Mempool')
const nbInputs = txInfo['inputs'].length
$('#tx-nb-inputs').text(nbInputs)
const nbOutputs = txInfo['outputs'].length
$('#tx-nb-outputs').text(nbOutputs)
$('#tx-vfeerate').text(`${txInfo['vfeerate']} sats/vbyte`)
const fees = parseInt(txInfo['fees'])
$('#tx-fees').text(`${fees} sats`)
let amount = fees
for (let o of txInfo['outputs']) {
amount += parseInt(o['value'])
}
amount = amount / 100000000
$('#tx-amount').text(`${amount} BTC`)
$('#tx-size').text(`${txInfo['size']} bytes`)
$('#tx-vsize').text(`${txInfo['vsize']} vbytes`)
$('#tx-version').text(txInfo['version'])
let nlocktime = parseInt(txInfo['locktime'])
if (nlocktime < 500000000) {
$('#tx-nlocktime').text(`Block ${nlocktime}`)
} else {
locktime = lib_fmt.unixTsToLocaleString(locktime)
$('#tx-nlocktime').text(locktime)
}
},
showSearchForm: function() {
$('#txs-tool-details').hide()
$('#txid').val('')
$('#txs-tool-search-form').show()
lib_msg.cleanMessagesUi()
},
showTxDetails: function() {
$('#txs-tool-search-form').hide()
$('#txs-tool-details').show()
},
}
screenScripts.set('#screen-txs-tools', screenTxsToolsScript)

42
static/admin/dmt/welcome/welcome.html

@ -0,0 +1,42 @@
<div id="welcome">
<h1>WELCOME!</h1>
<span>The Dojo's Maintenance Tool (DMT for short) provides a set of tools for monitoring and maintaining your Dojo.</span>
<span class="items-category ">MONITORING</span>
<span class="item">DOJO STATUS</span>
<span class="item-descr">A dashboard for monitoring the health of some components of your Dojo.</span>
<span class="item">PUSHTX STATUS</span>
<span class="item-descr">A dashboard for monitoring the transactions pushed through your Dojo.</span>
<span class="items-category ">TOOLS</span>
<span class="item">PAIRING</span>
<span class="item-descr">Pair your wallet to your Dojo by scanning a QRCode.</span>
<span class="item">XPUBS TOOL</span>
<span class="item-descr">Everything you need to manage your XPUBs manually.<br/>Check if a XPUB is tracked by your Dojo. Import and track a XPUB. Rescan the full history of a XPUB.</span>
<span class="item">ADDRESSES TOOL</span>
<span class="item-descr">Everything you need to manage your addresses manually.<br/>Check if an address is tracked by your Dojo. Import and track an address. Rescan the full history of an address.</span>
<span class="item">TRANSACTIONS TOOL</span>
<span class="item-descr">Check if a transaction is found in a block or in the mempool of your full node.</span>
<span class="item">BLOCKS RESCAN</span>
<span class="item-descr">Rescan the transactions confirmed by the blocks in a given range.</span>
<span class="items-category ">HELP</span>
<span class="item">DOJO TELEGRAM CHAT</span>
<span class="item-descr">Get support from the community for all things related to your Dojo (requires Telegram).</span>
<span class="item">WHIRLPOOL TELEGRAM CHAT</span>
<span class="item-descr">Get support from the community for all things related to Whirlpool (requires Telegram).</span>
<span class="item">SW TELEGRAM CHAT</span>
<span class="item-descr">Get support from the community for all things related to your Samourai Wallet (requires Telegram).</span>
</div>

177
static/admin/dmt/xpubs-tools/xpubs-tools.html

@ -0,0 +1,177 @@
<div id="xpubs-tool">
<h1>XPUBS TOOL</h1>
<div class="box-context">Check if a XPUB is tracked by your Dojo. Import and track a new XPUB. Rescan the full history of a XPUB.</div>
<div class="row box-main">
<!-- XPUB SEARCH FORM -->
<div id="xpubs-tool-search-form" class="fullwidth box">
<div class="box-body">
<span>Check if </span>
<input id="xpub" type="text" placeholder="XPUB">
<span> is tracked by your Dojo </span>
<button id="btn-xpub-search-go"
class="btn btn-success"
type="button">GO</button>
</div>
</div>
<!-- XPUB IMPORT -->
<div id="xpubs-tool-import" class="fullwidth box">
<div class="box-body">
<div id="import-deriv-first-import-msg">
<span>This XPUB isn't tracked by your Dojo. Do you want to import it and track its activity?</span>
</div>
<div id="import-deriv-reimport-msg">
<span>This XPUB is already tracked by your Dojo. Do you want to reimport it with a new derivation type?</span>
</div>
<div class="spacer20"></div>
<div>
<span>Import </span>
<span id="import-xpub"></span>
<span> with a </span>
<select id="import-deriv-type" type="select" value="auto">
<option value="auto" selected>auto</option>
<option value="bip44">BIP44</option>
<option value="bip49">BIP49</option>
<option value="bip84">BIP84</option>
</select>
<span> derivation</span>
<button id="btn-xpub-import-go" class="btn btn-success" type="button">IMPORT</button>
<button id="btn-xpub-import-cancel" class="btn btn-success" type="button">CANCEL</button>
</div>
</div>
</div>
<!-- XPUB DETAILS -->
<div id="xpubs-tool-details">
<div id="xpubs-tool-header" class="row box-main">
<div class="fullwidth box">
<div id="xpub-value" class="box-body center"></div>
</div>
</div>
<div id="xpubs-tool-actions" class="row box-main">
<div class="center">
<button id="btn-xpub-details-rescan" class="btn btn-success" type="button">RESCAN THIS XPUB</button>
<button id="btn-xpub-details-retype" class="btn btn-success" type="button">RETYPE THIS XPUB</button>
<button id="btn-xpub-details-reset" class="btn btn-success" type="button">SEARCH ANOTHER XPUB</button>
</div>
</div>
<div id="xpubs-rescans-actions" class="row box-main">
<div class="center">
<span>Rescan this xpub starting at index</span>
<input id="rescan-start-idx" type="text" value="0" placeholder="index">
<span> with a lookahead of </span>
<input id="rescan-lookahead" type="text" value="100" placeholder="#addresses">
<span> addresses</span>
<button id="btn-xpub-rescan-go" class="btn btn-success" type="button">RESCAN</button>
<button id="btn-xpub-rescan-cancel" class="btn btn-success" type="button">CANCEL</button>
</div>
</div>
<div id="xpubs-tool-details-row1" class="row box-main">
<!-- GENERAL INFO -->
<div id="box-general" class="halfwidth-left box">
<div class="box-header">GENERAL INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Derivation Type</td>
<td class="table-value" id="xpub-deriv-type"></td>
</tr>
<tr>
<td class="table-label">Balance</td>
<td class="table-value" id="xpub-balance"></td>
</tr>
<tr>
<td class="table-label">Number of Txs</td>
<td class="table-value" id="xpub-nb-txs"></td>
</tr>
<tr>
<td class="table-label">Number of UTXOs</td>
<td class="table-value" id="xpub-nb-utxos"></td>
</tr>
<tr>
<td class="table-label">Tracked since</td>
<td class="table-value" id="xpub-import-date"></td>
</tr>
</table>
</div>
</div>
<!-- DERIVATION INFO -->
<div id="box-derivation" class="halfwidth-right box">
<div class="box-header">XPUB DERIVATION INFO</div>
<div class="spacer10"></div>
<div class="box-body">
<table>
<tr>
<td class="table-label">Account</td>
<td class="table-value" id="xpub-deriv-account"></td>
<td class="table-label">Depth</td>
<td class="table-value" id="xpub-deriv-depth"></td>
</tr>
</table>
<div class="spacer10"></div>
<table id="table-deriv-idx">
<tr>
<td class="table-label" colspan="2">First unused indices</td>
<td class="table-label" colspan="2">Last derived indices</td>
</tr>
<tr>
<td class="table-label">External</td>
<td class="table-value" id="xpub-idx-unused-ext"></td>
<td class="table-label">External</td>
<td class="table-value" id="xpub-idx-derived-ext"></td>
</tr>
<tr>
<td class="table-label">Internal</td>
<td class="table-value" id="xpub-idx-unused-int"></td>
<td class="table-label">Internal</td>
<td class="table-value" id="xpub-idx-derived-int"></td>
</tr>
</table>
<div class="spacer10"></div>
</div>
</div>
</div>
<div id="xpubs-tool-details-row2" class="row box-main">
<!-- TXS LIST -->
<div id="box-txs" class="halfwidth-left box">
<div class="box-header">MOST RECENT TRANSACTIONS</div>
<div class="spacer10"></div>
<div class="box-body">
<table id="xpub-table-list-txs">
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
<!-- UTXOS LIST -->
<div id="box-utxos" class="halfwidth-right box">
<div class="box-header">UNSPENT TRANSACTION OUTPUTS</div>
<div class="spacer10"></div>
<div class="box-body">
<table id="xpub-table-list-utxos">
<tbody>
<tr>
<td></td>
<td></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
</div>
<script include-js="xpubs-tools/xpubs-tools.js"></script>

249
static/admin/dmt/xpubs-tools/xpubs-tools.js

@ -0,0 +1,249 @@
const screenXpubsToolsScript = {
explorerInfo: null,
currentXpub: null,
initPage: function() {
this.getExplorerInfo()
// Sets the event handlers
$('#btn-xpub-search-go').click(() => {this.searchXpub()})
$('#btn-xpub-details-reset').click(() => {this.showSearchForm()})
$('#btn-xpub-details-rescan').click(() => {this.showRescanForm()})
$('#btn-xpub-rescan-go').click(() => {this.rescanXpub()})
$('#btn-xpub-rescan-cancel').click(() => {this.hideRescanForm()})
$('#btn-xpub-import-go').click(() => {this.importXpub()})
$('#btn-xpub-details-retype').click(() => {this.showImportForm(true)})
$('#btn-xpub-import-cancel').click(() => {this.showSearchForm()})
$('#xpubs-tool').keyup(evt => {
if (evt.keyCode === 13) {
this.searchXpub()
}
})
},
preparePage: function() {
this.hideRescanForm()
this.showSearchForm()
$("#xpub").focus()
},
getExplorerInfo: function() {
lib_api.getExplorerPairingInfo().then(explorerInfo => {
this.explorerInfo = explorerInfo
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
searchXpub: function() {
lib_msg.displayMessage('Search in progress...');
const xpub = $('#xpub').val()
this.currentXpub = xpub
return this._searchXpub(xpub).then(() => {
lib_msg.cleanMessagesUi()
})
},
_searchXpub: function(xpub) {
return lib_api.getXpubInfo(xpub).then(xpubInfo => {
if (xpubInfo && xpubInfo['tracked']) {
this.setXpubDetails(xpubInfo)
this.showXpubDetails()
const jsonData = {'active': xpub}
return lib_api.getWallet(jsonData).then(walletInfo => {
// Display the txs
const txs = walletInfo['txs']
for (let tx of txs)
this.setTxDetails(tx)
// Display the UTXOs
const utxos = walletInfo['unspent_outputs'].sort((a,b) => {
return a['confirmations'] - b['confirmations']
})
$('#xpub-nb-utxos').text(utxos.length)
for (let utxo of utxos)
this.setUtxoDetails(utxo)
})
} else {
lib_msg.displayErrors('xpub not found')
this.showImportForm(false)
}
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
throw e
})
},
importXpub: function() {
lib_msg.displayMessage('Processing xpub import...');
const jsonData = {
'xpub': this.currentXpub,
'type': 'restore',
'force': true
}
const derivType = $('#import-deriv-type').val()
if (derivType == 'bip49' || derivType == 'bip84') {
jsonData['segwit'] = derivType
} else if (derivType == 'auto') {
if (this.currentXpub.startsWith('ypub'))
jsonData['segwit'] = 'bip49'
else if (this.currentXpub.startsWith('zpub'))
jsonData['segwit'] = 'bip84'
}
return lib_api.postXpub(jsonData)
.then(result => {
this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Import complete')
})
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
rescanXpub: function() {
lib_msg.displayMessage('Processing xpub rescan...');
let startIdx = $('#rescan-start-idx').val()
startIdx = (startIdx == null) ? 0 : parseInt(startIdx)
let lookahead = $('#rescan-lookahead').val()
lookahead = (lookahead == null) ? 100 : parseInt(lookahead)
return lib_api.getXpubRescan(this.currentXpub, lookahead, startIdx)
.then(result => {
this.hideRescanForm()
this._searchXpub(this.currentXpub).then(() => {
lib_msg.displayInfo('Rescan complete')
})
}).catch(e => {
lib_msg.displayErrors(lib_msg.extractJqxhrErrorMsg(e))
console.log(e)
})
},
setXpubDetails: function(xpubInfo) {
$('tr.tx-row').remove()
$('tr.utxo-row').remove()
$('#xpub-value').text(this.currentXpub)
$('#xpub-import-date').text(xpubInfo['created'])
$('#xpub-deriv-type').text(xpubInfo['derivation'])
$('#xpub-nb-txs').text(xpubInfo['n_tx'])
$('#xpub-nb-utxos').text('-')
const balance = parseInt(xpubInfo['balance']) / 100000000
$('#xpub-balance').text(`${balance} BTC`)
$('#xpub-deriv-account').text(xpubInfo['account'])
$('#xpub-deriv-depth').text(xpubInfo['depth'])
$('#xpub-idx-unused-ext').text(xpubInfo['unused']['external'])
$('#xpub-idx-derived-ext').text(xpubInfo['derived']['external'])
$('#xpub-idx-unused-int').text(xpubInfo['unused']['internal'])
$('#xpub-idx-derived-int').text(xpubInfo['derived']['internal'])
},
setTxDetails: function(tx) {
const txid = tx['hash']
const txidDisplay = `${txid.substring(0,50)}...`
const amount = parseInt(tx['result']) / 100000000
const amountLabel = amount < 0 ? amount : `+${amount}`
const amountStyle = amount < 0 ? 'amount-sent' : 'amount-received'
const date = lib_fmt.unixTsToLocaleString(tx['time'])
const txUrl = lib_cmn.getExplorerTxUrl(txid, this.explorerInfo)
const newRow = `<tr class="tx-row"><td colspan="2">&nbsp;</td></tr>
<tr class="tx-row">
<td class="table-label" colspan="2">
<a href="${txUrl}" target="_blank">${txidDisplay}</a>
</td>
</tr>
<tr class="tx-row">
<td class="table-label">Amount</td>
<td class="table-value ${amountStyle}">${amountLabel} BTC</td>
</tr>
<tr class="tx-row">
<td class="table-label">Block height</td>
<td class="table-value">${tx['block_height']}</td>
</tr>
<tr class="tx-row">
<td class="table-label">Date</td>
<td class="table-value">${date}</td>
</tr>`
$('#xpub-table-list-txs tr:last').after(newRow)
},
setUtxoDetails: function(utxo) {
const txid = utxo['tx_hash']
const txidVout = `${txid.substring(0,50)}...:${utxo['tx_output_n']}`
const amount = parseInt(utxo['value']) / 100000000
const txUrl = lib_cmn.getExplorerTxUrl(txid, this.explorerInfo)
const newRow = `<tr class="utxo-row"><td colspan="2">&nbsp;</td></tr>
<tr class="utxo-row">
<td class="table-label" colspan="2">
<a href="${txUrl}" target="_blank">${txidVout}</a>
</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Amount</td>
<td class="table-value">${amount} BTC</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Address</td>
<td class="table-value">${utxo['addr']}</td>
</tr>
<tr class="utxo-row">
<td class="table-label">Confirmations</td>
<td class="table-value">${utxo['confirmations']}</td>
</tr>`
$('#xpub-table-list-utxos tr:last').after(newRow)
},
showSearchForm: function() {
$('#xpubs-tool-details').hide()
$('#xpubs-tool-import').hide()
$('#xpub').val('')
$('#xpubs-tool-search-form').show()
lib_msg.cleanMessagesUi()
},
showImportForm: function(isReimport) {
$('#xpubs-tool-search-form').hide()
$('#xpubs-tool-details').hide()
if (isReimport) {
$('#import-deriv-first-import-msg').hide()
$('#import-deriv-reimport-msg').show()
} else {
$('#import-deriv-reimport-msg').hide()
$('#import-deriv-first-import-msg').show()
}
const xpubLen = this.currentXpub.length
const xpubShortLbl = `"${this.currentXpub.substring(0, 20)}...${this.currentXpub.substring(xpubLen-20, xpubLen)}"`
$('#import-xpub').text(xpubShortLbl)
$('#xpubs-tool-import').show()
},
showXpubDetails: function() {
$('#xpubs-tool-search-form').hide()
$('#xpubs-tool-import').hide()
$('#xpubs-tool-details').show()
},
showRescanForm: function() {
$('#xpubs-tool-actions').hide()
$('#xpubs-rescans-actions').show()
lib_msg.cleanMessagesUi()
},
hideRescanForm: function() {
$('#xpubs-rescans-actions').hide()
$('#xpubs-tool-actions').show()
},
}
screenScripts.set('#screen-xpubs-tools', screenXpubsToolsScript)

BIN
static/admin/icons/samourai-logo-loading.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

17
static/admin/index.html

@ -8,7 +8,7 @@
<link rel="stylesheet" type="text/css" href="css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="css/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="css/style.css">
<script src="lib/jquery-3.2.1.min.js"></script>
<script src="lib/jquery-3.5.1.min.js"></script>
<script src="conf/index.js"></script>
<script src="lib/common-script.js"></script>
<script src="lib/api-wrapper.js"></script>
@ -40,17 +40,10 @@
</div>
<div class="col-xs-4"></div>
</div>
<!-- MESSAGES -->
<div class="row msg-boxes">
<div class="col-xs-4"></div>
<div class="col-xs-4">
<div id="msg" class="msg"></div>
<div id="errors" class="msg-error"></div>
<div id="info" class="msg-info"></div>
</div>
<div class="col-xs-4"></div>
</div>
</div>
<!-- MSG BOX -->
<div id="box-msg"
include-html="dmt/msg-box/msg-box.html">
</div>
</body>

46
static/admin/index.js

@ -2,54 +2,56 @@
* Signin
*/
function login() {
let apiKey = $('#apikey').val();
let apiKey = $('#apikey').val()
let dataJson = {
'apikey': apiKey
};
}
// Checks input fields
if (!apiKey) {
lib_msg.displayErrors('Admin key is mandatory');
return;
lib_msg.displayErrors('Admin key is mandatory')
return
}
lib_msg.displayMessage('Processing...');
lib_msg.displayMessage('Processing...')
let deferred = lib_api.signin(dataJson);
let deferred = lib_api.signin(dataJson)
deferred.then(
function (result) {
const auth = result['authorizations'];
const accessToken = auth['access_token'];
const auth = result['authorizations']
const accessToken = auth['access_token']
if (lib_auth.isAdmin(accessToken)) {
lib_auth.setAccessToken(accessToken);
const refreshToken = auth['refresh_token'];
lib_auth.setRefreshToken(refreshToken);
sessionStorage.setItem('activeTab', '');
lib_msg.displayInfo('Successfully connected to your backend');
lib_auth.setAccessToken(accessToken)
const refreshToken = auth['refresh_token']
lib_auth.setRefreshToken(refreshToken)
sessionStorage.setItem('activeTab', '')
lib_msg.displayInfo('Successfully connected to your backend')
// Redirection to default page
lib_cmn.goToDefaultPage();
lib_cmn.goToDefaultPage()
} else {
lib_msg.displayErrors('You must sign in with the admin key');
lib_msg.displayErrors('You must sign in with the admin key')
}
},
function (jqxhr) {
let msg = lib_msg.extractJqxhrErrorMsg(jqxhr);
lib_msg.displayErrors(msg);
let msg = lib_msg.extractJqxhrErrorMsg(jqxhr)
lib_msg.displayErrors(msg)
}
);
)
}
/*
* onPageLoaded
*/
$(document).ready(function() {
// Dynamic loading of html and scripts
lib_cmn.includeHTML()
// Sets the event handlers
$('#apikey').keyup(function(evt) {
if (evt.keyCode === 13) {
login();
login()
}
});
$('#signin').click(function() {
login();
});
});
login()
})
})

136
static/admin/lib/api-wrapper.js

@ -1,4 +1,4 @@
var lib_api = {
const lib_api = {
/**
* Base URI
@ -9,156 +9,160 @@ var lib_api = {
* Authentication
*/
signin: function(data) {
let uri = this.baseUri + '/auth/login';
return this.sendPostUriEncoded(uri, data);
let uri = this.baseUri + '/auth/login'
return this.sendPostUriEncoded(uri, data)
},
/**
* Gets a new access token
*/
refreshToken: function(data) {
let uri = this.baseUri + '/auth/refresh';
return this.sendPostUriEncoded(uri, data);
let uri = this.baseUri + '/auth/refresh'
return this.sendPostUriEncoded(uri, data)
},
/**
* API Status
*/
getApiStatus: function() {
let prefix = conf['prefixes']['status'];
let uri = this.baseUri + '/' + prefix;
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['status']
let uri = this.baseUri + '/' + prefix
return this.sendGetUriEncoded(uri, {})
},
/**
* Get pairing info
*/
getPairingInfo: function() {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/pairing';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/pairing'
return this.sendGetUriEncoded(uri, {})
},
/**
* Get block explorer pairing info
*/
getExplorerPairingInfo: function() {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/pairing/explorer';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/pairing/explorer'
return this.sendGetUriEncoded(uri, {})
},
/**
* PushTx Status
*/
getPushtxStatus: function() {
let prefix = conf['prefixes']['statusPushtx'];
let uri = this.baseUri + '/pushtx/' + prefix;
//let uri = 'http://127.0.0.1:8081/' + prefix;
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['statusPushtx']
let uri = this.baseUri + '/pushtx/' + prefix
//let uri = 'http://127.0.0.1:8081/' + prefix
return this.sendGetUriEncoded(uri, {})
},
/**
* Orchestrztor Status
*/
getOrchestratorStatus: function() {
let prefix = conf['prefixes']['statusPushtx'];
let uri = this.baseUri + '/pushtx/' + prefix + '/schedule';
//let uri = 'http://127.0.0.1:8081/' + prefix + '/schedule';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['statusPushtx']
let uri = this.baseUri + '/pushtx/' + prefix + '/schedule'
//let uri = 'http://127.0.0.1:8081/' + prefix + '/schedule'
return this.sendGetUriEncoded(uri, {})
},
/**
* Gets information about an address
*/
getAddressInfo: function(address) {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/address/' + address + '/info';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/address/' + address + '/info'
return this.sendGetUriEncoded(uri, {})
},
/**
* Rescans an address
*/
getAddressRescan: function(address) {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/address/' + address + '/rescan';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/address/' + address + '/rescan'
return this.sendGetUriEncoded(uri, {})
},
/**
* Gets information about a xpub
*/
getXpubInfo: function(xpub) {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/info';
return this.sendGetUriEncoded(uri, {});
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/info'
return this.sendGetUriEncoded(uri, {})
},
/**
* Rescans a xpub
*/
getXpubRescan: function(xpub, nbAddr, startIdx) {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/rescan';
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/' + prefix + '/xpub/' + xpub + '/rescan'
return this.sendGetUriEncoded(
uri,
{
'gap': nbAddr,
'startidx': startIdx
}
);
)
},
/**
* Notifies the server of the new HD account for tracking.
*/
postXpub: function(arguments) {
let uri = this.baseUri + '/xpub';
return this.sendPostUriEncoded(uri, arguments);
let uri = this.baseUri + '/xpub'
return this.sendPostUriEncoded(uri, arguments)
},
/**
* Wallet
*/
getWallet: function(arguments) {
let uri = this.baseUri + '/wallet';
return this.sendGetUriEncoded(uri, arguments);
let uri = this.baseUri + '/wallet'
return this.sendGetUriEncoded(uri, arguments)
},
/**
* Transaction
*/
getTransaction: function(txid) {
let uri = this.baseUri + '/tx/' + txid;
return this.sendGetUriEncoded(uri, {});
let uri = this.baseUri + '/tx/' + txid
return this.sendGetUriEncoded(
uri,
{
'fees': 1
}
)
},
/**
* Rescans a range of blocks
*/
getBlocksRescan: function(fromHeight, toHeight) {
let prefix = conf['prefixes']['support'];
let uri = this.baseUri + '/tracker/' + prefix + '/rescan';
//let uri = 'http://127.0.0.1:8082/' + prefix + '/rescan';
let prefix = conf['prefixes']['support']
let uri = this.baseUri + '/tracker/' + prefix + '/rescan'
//let uri = 'http://127.0.0.1:8082/' + prefix + '/rescan'
return this.sendGetUriEncoded(
uri,
{
'fromHeight': fromHeight,
'toHeight': toHeight
}
);
)
},
/**
* HTTP requests methods
*/
sendGetUriEncoded: function(uri, data) {
data['at'] = lib_auth.getAccessToken();
data['at'] = lib_auth.getAccessToken()
let deferred = $.Deferred(),
dataString = $.param(data);
dataString = $.param(data)
$.when($.ajax({
url: uri,
@ -167,20 +171,20 @@ var lib_api = {
contentType: "application/x-www-form-urlencoded; charset=utf-8"
}))
.done(function (result) {
deferred.resolve(result);
deferred.resolve(result)
})
.fail(function (jqxhr, textStatus, error) {
deferred.reject(jqxhr);
});
deferred.reject(jqxhr)
})
return deferred.promise();
return deferred.promise()
},
sendPostUriEncoded: function(uri, data) {
data['at'] = lib_auth.getAccessToken();
data['at'] = lib_auth.getAccessToken()
let deferred = $.Deferred(),
dataString = $.param(data);
dataString = $.param(data)
$.when($.ajax({
url: uri,
@ -189,19 +193,19 @@ var lib_api = {
contentType: "application/x-www-form-urlencoded; charset=utf-8"
}))
.done(function (result) {
deferred.resolve(result);
deferred.resolve(result)
})
.fail(function (jqxhr, textStatus, error) {
deferred.reject(jqxhr);
deferred.reject(jqxhr)
});
return deferred.promise();
return deferred.promise()
},
sendGetJson: function(uri, data) {
data['at'] = lib_auth.getAccessToken();
data['at'] = lib_auth.getAccessToken()
let deferred = $.Deferred();
let deferred = $.Deferred()
$.when($.ajax({
url: uri,
@ -209,21 +213,21 @@ var lib_api = {
data: data,
}))
.done(function (result) {
deferred.resolve(result);
deferred.resolve(result)
})
.fail(function (jqxhr, textStatus, error) {
deferred.reject(jqxhr);
deferred.reject(jqxhr)
});
return deferred.promise();
return deferred.promise()
},
sendPostJson: function(uri, data) {
data['at'] = lib_auth.getAccessToken();
data['at'] = lib_auth.getAccessToken()
let deferred = $.Deferred(),
dataString = JSON.stringify(data);
dataString = JSON.stringify(data)
$.when($.ajax({
url: uri,
@ -233,13 +237,13 @@ var lib_api = {
dataType: 'json'
}))
.done(function (result) {
deferred.resolve(result);
deferred.resolve(result)
})
.fail(function (jqxhr, textStatus, error) {
deferred.reject(jqxhr);
deferred.reject(jqxhr)
});
return deferred.promise();
return deferred.promise()
}
}

64
static/admin/lib/auth-utils.js

@ -1,4 +1,4 @@
var lib_auth = {
const lib_auth = {
/* SessionStorage Key used for access token */
SESSION_STORE_ACCESS_TOKEN: 'access_token',
@ -20,7 +20,7 @@ var lib_auth = {
* Retrieves access token from session storage
*/
getAccessToken: function() {
return sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN);
return sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN)
},
/*
@ -28,22 +28,22 @@ var lib_auth = {
*/
setAccessToken: function(token) {
const now = new Date();
sessionStorage.setItem(this.SESSION_STORE_ACCESS_TOKEN_TS, now.getTime());
sessionStorage.setItem(this.SESSION_STORE_ACCESS_TOKEN, token);
sessionStorage.setItem(this.SESSION_STORE_ACCESS_TOKEN_TS, now.getTime())
sessionStorage.setItem(this.SESSION_STORE_ACCESS_TOKEN, token)
},
/*
* Retrieves refresh token from session storage
*/
getRefreshToken: function() {
return sessionStorage.getItem(this.SESSION_STORE_REFRESH_TOKEN);
return sessionStorage.getItem(this.SESSION_STORE_REFRESH_TOKEN)
},
/*
* Stores refresh token in session storage
*/
setRefreshToken: function(token) {
sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN, token);
sessionStorage.setItem(this.SESSION_STORE_REFRESH_TOKEN, token)
},
/*
@ -51,28 +51,28 @@ var lib_auth = {
*/
refreshAccessToken: function() {
if (!this.isAuthenticated()) {
return;
return
}
const now = new Date();
const atts = sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN_TS);
const timeElapsed = (now.getTime() - atts) / 1000;
const atts = sessionStorage.getItem(this.SESSION_STORE_ACCESS_TOKEN_TS)
const timeElapsed = (now.getTime() - atts) / 1000
// Refresh the access token if more than 10mn
if (timeElapsed > 600) {
// Refresh the access token if more than 5mn
if (timeElapsed > 300) {
const dataJson = {
'rt': this.getRefreshToken()
};
}
let self = this;
let self = this
let deferred = lib_api.refreshToken(dataJson);
let deferred = lib_api.refreshToken(dataJson)
deferred.then(
function (result) {
const auth = result['authorizations'];
const accessToken = auth['access_token'];
self.setAccessToken(accessToken);
const auth = result['authorizations']
const accessToken = auth['access_token']
self.setAccessToken(accessToken)
},
function (jqxhr) {
// Do nothing
@ -86,8 +86,8 @@ var lib_auth = {
*/
isAuthenticated: function() {
// Checks that an access token is stored in session storage
let token = this.getAccessToken();
return (token && (token != 'null')) ? true : false;
let token = this.getAccessToken()
return (token && (token != 'null')) ? true : false
},
/*
@ -96,17 +96,17 @@ var lib_auth = {
*/
getPayloadAccessToken: function(token) {
if (!token)
token = this.getAccessToken();
token = this.getAccessToken()
if (!token)
return null;
return null
try {
const payloadBase64 = token.split('.')[1];
const payloadUtf8 = atob(payloadBase64);
return JSON.parse(payloadUtf8);
const payloadBase64 = token.split('.')[1]
const payloadUtf8 = atob(payloadBase64)
return JSON.parse(payloadUtf8)
} catch {
return null;
return null
}
},
@ -114,10 +114,10 @@ var lib_auth = {
* Check if user has admin profile
*/
isAdmin: function(token) {
const payload = this.getPayloadAccessToken(token);
const payload = this.getPayloadAccessToken(token)
if (!payload)
return false;
return (('prf' in payload) && (payload['prf'] == this.TOKEN_PROFILE_ADMIN));
return false
return (('prf' in payload) && (payload['prf'] == this.TOKEN_PROFILE_ADMIN))
},
/*
@ -125,10 +125,10 @@ var lib_auth = {
*/
logout: function() {
// Clears session storage
this.setRefreshToken(null);
this.setAccessToken(null);
sessionStorage.setItem('activeTab', '');
lib_cmn.goToHomePage();
this.setRefreshToken(null)
this.setAccessToken(null)
sessionStorage.setItem('activeTab', '')
lib_cmn.goToHomePage()
}
}

2377
static/admin/lib/bootstrap.js

File diff suppressed because it is too large

120
static/admin/lib/common-script.js

@ -1,51 +1,125 @@
lib_cmn = {
const lib_cmn = {
// Utils functions
hasProperty: function(obj, propName) {
/* Checks if an object has a property with given name */
if ( (obj == null) || (!propName) )
return false;
return false
else if (obj.hasOwnProperty('propName') || propName in obj)
return true;
return true
else
return false;
return false
},
// Go to default page
goToDefaultPage: function() {
const baseUri = conf['adminTool']['baseUri'];
sessionStorage.setItem('activeTab', '#link-pairing');
window.location = baseUri + '/tool/';
const baseUri = conf['adminTool']['baseUri']
sessionStorage.setItem('activeTab', '#link-status')
window.location = baseUri + '/dmt/'
},
// Go to home page
goToHomePage: function() {
sessionStorage.setItem('activeTab', null);
window.location = conf['adminTool']['baseUri'] + '/';
sessionStorage.setItem('activeTab', null)
window.location = conf['adminTool']['baseUri'] + '/'
},
// Loads html snippet
// Get Transaction url on selected explorer
getExplorerTxUrl: function(txid, explorerInfo) {
if (explorerInfo == null)
return null
else if (explorerInfo['pairing']['type'] == 'explorer.oxt')
return `${explorerInfo['pairing']['url']}/transaction/${txid}`
else if (explorerInfo['pairing']['type'] == 'explorer.btc_rpc_explorer')
return `http://${explorerInfo['pairing']['url']}/tx/${txid}`
else
return null
},
// Loads html snippets
includeHTML: function(cb) {
let self = this;
let z, i, elmnt, file, xhttp;
z = document.getElementsByTagName('*');
let self = this
let z, i, elmnt, file, xhttp
z = document.getElementsByTagName('*')
for (i = 0; i < z.length; i++) {
elmnt = z[i]
file = elmnt.getAttribute('include-html')
if (file) {
xhttp = new XMLHttpRequest()
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
elmnt.innerHTML = this.responseText
elmnt.removeAttribute('include-html')
self.includeHTML(cb)
self.includeJs(elmnt)
}
}
xhttp.open('GET', file, true)
xhttp.send()
return
}
}
if (cb) cb()
},
// Loads js snippets
includeJs: function(element) {
let self = this
let z, i, elmnt, file, xhttp
z = element.querySelectorAll('script')
for (i = 0; i < z.length; i++) {
elmnt = z[i];
file = elmnt.getAttribute('include-html');
elmnt = z[i]
file = elmnt.getAttribute('include-js')
if (file) {
xhttp = new XMLHttpRequest();
xhttp = new XMLHttpRequest()
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 200) {
elmnt.innerHTML = this.responseText;
elmnt.removeAttribute('include-html');
self.includeHTML(cb);
const newElmnt = document.createElement('script')
newElmnt.textContent = this.responseText
if (elmnt.parentNode) {
elmnt.parentNode.insertBefore(newElmnt, elmnt.nextSibling)
elmnt.parentNode.removeChild(elmnt)
}
}
}
xhttp.open('GET', file, true);
xhttp.send();
return;
xhttp.open('GET', file, true)
xhttp.send()
return
}
}
if (cb) cb();
},
pad10: function(v) {
return (v < 10) ? `0${v}` : `${v}`
},
pad100: function(v) {
if (v < 10) return `00${v}`
if (v < 100) return `0${v}`
return `${v}`
},
timePeriod: function(period, milliseconds) {
milliseconds = !!milliseconds
const whole = Math.floor(period)
const ms = 1000*(period - whole)
const s = whole % 60
const m = (whole >= 60) ? Math.floor(whole / 60) % 60 : 0
const h = (whole >= 3600) ? Math.floor(whole / 3600) % 24 : 0
const d = (whole >= 86400) ? Math.floor(whole / 86400) : 0
const parts = [this.pad10(h), this.pad10(m), this.pad10(s)]
if (d > 0)
parts.splice(0, 0, this.pad100(d))
const str = parts.join(':')
if (milliseconds) {
return str + '.' + this.pad100(ms)
} else {
return str
}
}
}

39
static/admin/lib/format-utils.js

@ -1,13 +1,13 @@
lib_fmt = {
const lib_fmt = {
/*
* Returns a stringified version of a cleaned json object
*/
cleanJson: function(json) {
let jsonText = JSON.stringify(json);
jsonText = jsonText.replace(/'/g, '"').replace(/False/g, 'false').replace(/True/g, 'true');
jsonText = jsonText.replace(/(Decimal\(")([0-9.E\-,]*)("\))/g, '"$2"');
return jsonText;
let jsonText = JSON.stringify(json)
jsonText = jsonText.replace(/'/g, '"').replace(/False/g, 'false').replace(/True/g, 'true')
jsonText = jsonText.replace(/(Decimal\(")([0-9.E\-,]*)("\))/g, '"$2"')
return jsonText
},
/*
@ -15,37 +15,37 @@ lib_fmt = {
*/
jsonSyntaxHighlight: function(json) {
if (typeof json != 'string') {
json = JSON.stringify(json, undefined, 2);
json = JSON.stringify(json, undefined, 2)
}
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
json = json.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
return json.replace(
/("(\\u[a-zA-Z0-9]{4}|\\[^u]|[^\\"])*"(\s*:)?|\b(true|false|null)\b|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?)/g,
function (match) {
let cls = 'number';
let cls = 'number'
if (/^"/.test(match)) {
if (/:$/.test(match)) {
cls = 'key';
cls = 'key'
} else {
cls = 'string';
cls = 'string'
}
} else if (/true|false/.test(match)) {
cls = 'boolean';
cls = 'boolean'
} else if (/null/.test(match)) {
cls = 'null';
cls = 'null'
}
return '<span class="' + cls + '">' + match + '</span>';
return '<span class="' + cls + '">' + match + '</span>'
}
);
)
},
/*
* Format a unix timestamp to locale date string
*/
unixTsToLocaleString: function(ts) {
let tmpDate = new Date(ts*1000);
return tmpDate.toLocaleString();
let tmpDate = new Date(ts*1000)
return tmpDate.toLocaleString()
},
/*
@ -53,10 +53,11 @@ lib_fmt = {
*/
formatUnixTs: function(ts) {
if (ts == null || ts == 0)
return '-';
return '-'
let tmpDate = new Date(ts*1000),
options = {hour: '2-digit', minute: '2-digit', hour12: false};
return tmpDate.toLocaleDateString('fr-FR', options);
options = {hour: '2-digit', minute: '2-digit', hour12: false}
return tmpDate.toLocaleDateString('fr-FR', options)
}
}

4
static/admin/lib/jquery-3.2.1.min.js

File diff suppressed because one or more lines are too long

2
static/admin/lib/jquery-3.5.1.min.js

File diff suppressed because one or more lines are too long

34
static/admin/lib/messages.js

@ -1,39 +1,41 @@
var lib_msg = {
const lib_msg = {
// Extracts jqxhr error message
extractJqxhrErrorMsg: function(jqxhr) {
let hasErrorMsg = ('responseJSON' in jqxhr) &&
(jqxhr['responseJSON'] != null) &&
('message' in jqxhr['responseJSON']);
('error' in jqxhr['responseJSON'])
return hasErrorMsg ? jqxhr['responseJSON']['message'] : jqxhr.statusText;
return hasErrorMsg ? jqxhr['responseJSON']['error'] : jqxhr.statusText
},
// UI functions
addTextinID: function(text, id){
$(id).html(text.toUpperCase());
$(id).html(text.toUpperCase())
},
displayMessage: function(text){
this.addTextinID('', '#errors');
this.addTextinID('', '#info');
this.addTextinID(text, '#msg');
this.addTextinID('', '#errors')
this.addTextinID('', '#info')
this.addTextinID(text, '#msg')
},
displayErrors: function(text){
this.addTextinID('', '#msg');
this.addTextinID('', '#info');
this.addTextinID(text, '#errors');
this.addTextinID('', '#msg')
this.addTextinID('', '#info')
this.addTextinID(text, '#errors')
},
displayInfo: function(text){
this.addTextinID('', '#msg');
this.addTextinID('', '#errors');
this.addTextinID(text, '#info');
this.addTextinID('', '#msg')
this.addTextinID('', '#errors')
this.addTextinID(text, '#info')
},
cleanMessagesUi: function() {
this.addTextinID('', '#msg');
this.addTextinID('', '#errors');
this.addTextinID('', '#info');
this.addTextinID('', '#msg')
this.addTextinID('', '#errors')
this.addTextinID('', '#info')
}
}

132
static/admin/tool/index.html

@ -1,132 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>DOJO // MAINTENANCE TOOL</title>
<link rel="stylesheet" type="text/css" href="../css/bootstrap.min.css">
<link rel="stylesheet" type="text/css" href="../css/bootstrap-theme.min.css">
<link rel="stylesheet" type="text/css" href="../css/style.css">
<script src="../lib/jquery-3.2.1.min.js"></script>
<script src="../lib/jquery.qrcode.min.js"></script>
<script src="../conf/index.js"></script>
<script src="../lib/common-script.js"></script>
<script src="../lib/api-wrapper.js"></script>
<script src="../lib/auth-utils.js"></script>
<script src="../lib/format-utils.js"></script>
<script src="index.js"></script>
</head>
<body>
<div id="info-xpub" class="container">
<!-- HEADER -->
<div id="header" class="row">
<div class="col-xs-9">
<h1 class="title"><span>DOJO // MAINTENANCE TOOL</span> <span id="dojo-version" class="beta">beta</span></h1>
</div>
<div class="col-xs-3 login-box">
<a id="btn-logout" style="display: inline;" href="#" title="DISCONNECT">
<img src="../icons/ic_power_settings_new_white_24dp_1x.png" class="mini-icon"/>
</a>
</div>
</div>
<div class="spacer60"></div>
<!-- TAB MENU -->
<div id="tab-menu" class="row">
<div class="col-xs-12" >
<ul id="tab-menu_list" class="nav nav-pills">
<li id="link-pairing">
<a href="#">PAIRING</a>
</li>
<li id="link-status-api">
<a href="#">API</a>
</li>
<li id="link-status-pushtx">
<a href="#">PUSHTX</a>
</li>
<li id="link-orchestrator">
<a href="#">ORCHESTRATOR</a>
</li>
<li id="link-info-xpub">
<a href="#">XPUB INFO</a>
</li>
<li id="link-rescan-xpub">
<a href="#">XPUB RESCAN</a>
</li>
<li id="link-xpub">
<a href="#">XPUB</a>
</li>
<li id="link-info-address">
<a href="#">ADDR. INFO</a>
</li>
<li id="link-rescan-address">
<a href="#">ADDR. RESCAN</a>
</li>
<li id="link-wallet">
<a href="#">WALLET</a>
</li>
<li id="link-tx">
<a href="#">TX</a>
</li>
<li id="link-rescan-blocks">
<a href="#">BLOCKS RESCAN</a>
</li>
</ul>
</div>
</div>
<!-- BODY -->
<div id="body" class="row">
<div class="col-xs-1"></div>
<div class="col-xs-10 json-data-container">
<!-- PAIRING -->
<div id="screen-pairing">
<div class="row">
<div id="qr-label" class="halfwidth">
PAIR YOUR WALLET WITH YOUR DOJO
</div>
<div id="qr-explorer-label" class="halfwidth">
PAIR YOUR WALLET WITH YOUR BLOCK EXPLORER
</div>
</div>
<div class="row">
<div id="qr-container" class="halfwidth">
<div id="qr-pairing"></div>
</div>
<div id="qr-explorer-container" class="halfwidth">
<div id="qr-explorer-pairing"></div>
</div>
</div>
</div>
<!-- MAINTENANCE -->
<div id="form-maintenance">
<div id="row-form-field">
<div id="cell-args">
<input type="text" id="args" placeholder="">
</div>
<div id="cell-args2">
<input type="text" id="args2" placeholder="">
</div>
<div id="cell-args3">
<input type="text" id="args3" placeholder="">
</div>
</div>
<div id="row-form-button" class="center">
<button id="btn-go"
class="btn btn-success"
type="button">GO</button>
</div>
<div class="center">
<pre id="json-data" style="min-height: 300px"></pre>
</div>
</div>
</div>
<div class="col-xs-1"></div>
</div>
</div>
</body>
</html>

295
static/admin/tool/index.js

@ -1,295 +0,0 @@
/**
* Display Messages
*/
function displayInfoMsg(msg) {
const htmlMsg = '<span class="info">' + msg + '</span>';
$('#json-data').html(htmlMsg);
}
function displayErrorMsg(msg) {
const htmlMsg = '<span class="error">' + msg + '</span>';
$('#json-data').html(htmlMsg);
}
function displayQRPairing() {
const activeTab = sessionStorage.getItem('activeTab');
processAction(activeTab).then(
function (result) {
if (result) {
if (result['api']) {
const textJson = JSON.stringify(result['api'], null, 4);
$("#qr-pairing").html('') // clear qrcode first
$('#qr-pairing').qrcode({width: 256, height: 256, text: textJson});
}
if (result['explorer'] && result['explorer']['pairing']['url']) {
const textJson = JSON.stringify(result['explorer'], null, 4);
$("#qr-explorer-pairing").html('') // clear qrcode first
$('#qr-explorer-pairing').qrcode({width: 256, height: 256, text: textJson});
} else {
$("#qr-label").removeClass('halfwidth');
$("#qr-label").addClass('fullwidth');
$("#qr-container").removeClass('halfwidth');
$("#qr-container").addClass('fullwidth');
$("#qr-explorer-label").hide();
$("#qr-explorer-container").hide();
}
}
},
function (jqxhr) {}
);
}
/**
* On tab switched
*/
function initTabs() {
// Activates the current tab
let currentTab = sessionStorage.getItem('activeTab');
if (!currentTab) {
currentTab = '#link-pairing';
}
$(currentTab).addClass('active');
const tabs = [
'#link-pairing',
'#link-status-api',
'#link-status-pushtx',
'#link-orchestrator',
'#link-info-xpub',
'#link-rescan-xpub',
'#link-xpub',
'#link-info-address',
'#link-rescan-address',
'#link-rescan-blocks',
'#link-wallet',
'#link-tx'
];
// Sets event handlers
for (let tab of tabs) {
$(tab).click(function() {
$(sessionStorage.getItem('activeTab')).removeClass('active');
sessionStorage.setItem('activeTab', tab);
$(tab).addClass('active');
preparePage();
});
}
}
/**
* Prepares the page content
*/
function preparePage() {
const activeTab = sessionStorage.getItem('activeTab');
// Dojo version
let lblVersion = sessionStorage.getItem('lblVersion');
if (lblVersion == null) {
lib_api.getPairingInfo().then(apiInfo => {
lblVersion = 'v' + apiInfo['pairing']['version'] + ' beta';
sessionStorage.setItem('lblVersion', lblVersion);
$('#dojo-version').text(lblVersion);
});
} else {
$('#dojo-version').text(lblVersion);
}
// Pairing
if (activeTab == '#link-pairing') {
$('#screen-pairing').show();
$('#form-maintenance').hide();
displayQRPairing();
// Maintenance screens
} else {
$('#form-maintenance').show();
$('#screen-pairing').hide();
let placeholder = '',
placeholder2 = '',
placeholder3 = '';
$("#cell-args").removeClass('halfwidth');
$("#cell-args").addClass('fullwidth');
$("#cell-args2").hide();
$("#cell-args3").hide();
if (activeTab == '#link-status-api' ||
activeTab == '#link-status-pushtx' ||
activeTab == '#link-orchestrator'
) {
$("#row-form-field").hide();
$("#row-form-button").hide();
processGo();
} else {
$("#row-form-field").show();
$("#row-form-button").show();
}
if (activeTab == '#link-info-xpub') {
placeholder = 'ENTER A XPUB, YPUB OR ZPUB';
} else if (activeTab == '#link-xpub') {
placeholder = 'ENTER /XPUB URL ARGUMENTS (e.g.: xpub=xpub0123456789&segwit=bip84&type=restore&force=true)';
} else if (activeTab == '#link-info-address') {
placeholder = 'ENTER A BITCOIN ADDRESS';
} else if (activeTab == '#link-rescan-address') {
placeholder = 'ENTER A BITCOIN ADDRESS';
} else if (activeTab == '#link-rescan-blocks') {
$("#cell-args").removeClass('fullwidth');
$("#cell-args").addClass('halfwidth');
$("#cell-args2").show();
placeholder = 'RESCAN BLOCKS FROM HEIGHT...';
placeholder2 = '...TO HEIGHT (OPTIONAL)';
} else if (activeTab == '#link-wallet') {
placeholder = 'ENTER /WALLET URL ARGUMENTS (e.g.: active=xpub0123456789&new=address2|address3&pubkey=pubkey4)';
} else if (activeTab == '#link-tx') {
placeholder = 'ENTER A TRANSACTION TXID';
} else if (activeTab == '#link-rescan-xpub') {
$("#cell-args").removeClass('fullwidth');
$("#cell-args").addClass('halfwidth');
$("#cell-args2").show();
$("#cell-args3").show();
placeholder = 'ENTER A XPUB, YPUB OR ZPUB';
placeholder2 = 'ENTER #ADDR. (DEFAULT=100)';
placeholder3 = 'ENTER START INDEX (DEFAULT=0)';
}
$("#args").attr('placeholder', placeholder);
$('#args').val('');
$("#args2").attr('placeholder', placeholder2);
$('#args2').val('');
$("#args3").attr('placeholder', placeholder3);
$('#args3').val('');
$('#json-data').html('');
}
}
/**
* Process action (api calls)
*/
function processAction(activeTab, args, args2, args3) {
if (activeTab == '#link-pairing') {
//return lib_api.getPairingInfo();
let result = {
'api': null,
'explorer': null
};
return lib_api.getPairingInfo().then(apiInfo => {
if (apiInfo) {
apiInfo['pairing']['url'] = window.location.protocol + '//' + window.location.host + conf['api']['baseUri'];
result['api'] = apiInfo;
}
}).then(() => {
return lib_api.getExplorerPairingInfo();
}).then(explorerInfo => {
if (explorerInfo)
result['explorer'] = explorerInfo;
return result
}).catch(e => {
console.log(e);
return result;
});
} else if (activeTab == '#link-status-api') {
return lib_api.getApiStatus();
} else if (activeTab == '#link-status-pushtx') {
return lib_api.getPushtxStatus();
} else if (activeTab == '#link-orchestrator') {
return lib_api.getOrchestratorStatus();
}
if (args == '') {
alert('Argument is mandatory');
return;
}
if (activeTab == '#link-info-xpub') {
return lib_api.getXpubInfo(args);
} else if (activeTab == '#link-rescan-xpub') {
const nbAddr = (!args2) ? 100 : parseInt(args2);
const startIdx = (!args3) ? 0 : parseInt(args3);
return lib_api.getXpubRescan(args, nbAddr, startIdx);
} else if (activeTab == '#link-info-address') {
return lib_api.getAddressInfo(args);
} else if (activeTab == '#link-rescan-address') {
return lib_api.getAddressRescan(args);
} else if (activeTab == '#link-rescan-blocks') {
const fromHeight = parseInt(args);
const toHeight = (args2) ? parseInt(args2) : fromHeight;
return lib_api.getBlocksRescan(fromHeight, toHeight);
} else if (activeTab == '#link-tx') {
return lib_api.getTransaction(args);
}
const jsonData = {};
const aArgs = args.split('&');
for (let arg of aArgs) {
const aArg = arg.split('=');
jsonData[aArg[0]] = aArg[1];
}
if (activeTab == '#link-wallet')
return lib_api.getWallet(jsonData);
else if (activeTab == '#link-xpub')
return lib_api.postXpub(jsonData);
}
/**
* Retrieve information about the xpub
*/
function processGo() {
const activeTab = sessionStorage.getItem('activeTab');
const args = $("#args").val();
const args2 = $("#args2").val();
const args3 = $("#args3").val();
displayInfoMsg('Processing...');
let deferred = processAction(activeTab, args, args2, args3);
deferred.then(
function (result) {
if (!result)
return;
let textJson = lib_fmt.cleanJson(result);
textJson = JSON.stringify(JSON.parse(textJson), null, 4);
textJson = lib_fmt.jsonSyntaxHighlight(textJson);
$('#json-data').html(textJson);
},
function (jqxhr) {
let hasErrorMsg =
('responseJSON' in jqxhr) &&
(jqxhr['responseJSON'] != null) &&
('message' in jqxhr['responseJSON']);
const msg = hasErrorMsg ? jqxhr['responseJSON']['message'] : jqxhr.statusText;
displayErrorMsg(msg);
}
);
}
/**
* Processing on loading completed
*/
$(document).ready(function() {
// Refresh the access token if needed
setInterval(() => {
lib_auth.refreshAccessToken();
}, 300000);
initTabs();
preparePage();
// Sets the event handlers
$('#args').keyup(function(evt) {
if (evt.keyCode === 13) {
processGo();
}
});
$('#btn-go').click(function() {
processGo();
});
$('#btn-logout').click(function() {
lib_auth.logout();
});
});
Loading…
Cancel
Save