Browse Source

dashboard is its own repo from now on

master
Daniel Huebleitner 2 years ago
commit
31446eb747
75 changed files with 3172 additions and 0 deletions
  1. 13
    0
      .editorconfig
  2. 46
    0
      .gitignore
  3. 27
    0
      README.md
  4. 130
    0
      angular.json
  5. 12
    0
      browserslist
  6. 32
    0
      e2e/protractor.conf.js
  7. 23
    0
      e2e/src/app.e2e-spec.ts
  8. 11
    0
      e2e/src/app.po.ts
  9. 13
    0
      e2e/tsconfig.json
  10. 32
    0
      karma.conf.js
  11. 62
    0
      package.json
  12. 9
    0
      proxy.conf.json
  13. 93
    0
      src/app/ApiClient/apiclient-consumptions.component.ts
  14. 132
    0
      src/app/ApiClient/apiclient-settings-form.component.ts
  15. 103
    0
      src/app/ApiClient/apiclient-subscriptions.component.ts
  16. 51
    0
      src/app/ApiClient/module.ts
  17. 47
    0
      src/app/PluginManager/debug.component.ts
  18. 45
    0
      src/app/PluginManager/module.ts
  19. 156
    0
      src/app/PluginManager/plugins.component.ts
  20. 174
    0
      src/app/Wallet/btc/address-viewer.component.ts
  21. 566
    0
      src/app/Wallet/btc/btc.component.ts
  22. 266
    0
      src/app/Wallet/btc/wallet-generator.component.ts
  23. 50
    0
      src/app/Wallet/btc/wallet-picker.component.ts
  24. 60
    0
      src/app/Wallet/module.ts
  25. 24
    0
      src/app/app-routing.module.ts
  26. 5
    0
      src/app/app.component.html
  27. 0
    0
      src/app/app.component.scss
  28. 35
    0
      src/app/app.component.spec.ts
  29. 32
    0
      src/app/app.component.ts
  30. 69
    0
      src/app/app.module.ts
  31. 3
    0
      src/app/content-area/main.component.html
  32. 0
    0
      src/app/content-area/main.component.scss
  33. 25
    0
      src/app/content-area/main.component.spec.ts
  34. 17
    0
      src/app/content-area/main.component.ts
  35. 4
    0
      src/app/dynamic-loader/dynamic-loader.component.html
  36. 3
    0
      src/app/dynamic-loader/dynamic-loader.component.scss
  37. 25
    0
      src/app/dynamic-loader/dynamic-loader.component.spec.ts
  38. 115
    0
      src/app/dynamic-loader/dynamic-loader.component.ts
  39. 15
    0
      src/app/error-display/a-nested.component.ts
  40. 1
    0
      src/app/error-display/error-display.component.html
  41. 0
    0
      src/app/error-display/error-display.component.scss
  42. 25
    0
      src/app/error-display/error-display.component.spec.ts
  43. 15
    0
      src/app/error-display/error-display.component.ts
  44. 22
    0
      src/app/error-display/module.ts
  45. 12
    0
      src/app/header-bar/header-bar.component.html
  46. 0
    0
      src/app/header-bar/header-bar.component.scss
  47. 25
    0
      src/app/header-bar/header-bar.component.spec.ts
  48. 15
    0
      src/app/header-bar/header-bar.component.ts
  49. 1
    0
      src/app/home/home.component.html
  50. 0
    0
      src/app/home/home.component.scss
  51. 25
    0
      src/app/home/home.component.spec.ts
  52. 15
    0
      src/app/home/home.component.ts
  53. 20
    0
      src/app/sidebar-entry-service.service.ts
  54. 19
    0
      src/app/sidebar/sidebar.component.html
  55. 0
    0
      src/app/sidebar/sidebar.component.scss
  56. 25
    0
      src/app/sidebar/sidebar.component.spec.ts
  57. 24
    0
      src/app/sidebar/sidebar.component.ts
  58. 7
    0
      src/app/subnav/subnav.component.html
  59. 0
    0
      src/app/subnav/subnav.component.scss
  60. 25
    0
      src/app/subnav/subnav.component.spec.ts
  61. 15
    0
      src/app/subnav/subnav.component.ts
  62. 1
    0
      src/assets/.gitkeep
  63. 19
    0
      src/assets/FrontblockLib.js
  64. 3
    0
      src/environments/environment.prod.ts
  65. 16
    0
      src/environments/environment.ts
  66. BIN
      src/favicon.ico
  67. 15
    0
      src/index.html
  68. 12
    0
      src/main.ts
  69. 67
    0
      src/polyfills.ts
  70. 52
    0
      src/styles.scss
  71. 20
    0
      src/test.ts
  72. 15
    0
      tsconfig.app.json
  73. 26
    0
      tsconfig.json
  74. 18
    0
      tsconfig.spec.json
  75. 92
    0
      tslint.json

+ 13
- 0
.editorconfig View File

@@ -0,0 +1,13 @@
1
+# Editor configuration, see https://editorconfig.org
2
+root = true
3
+
4
+[*]
5
+charset = utf-8
6
+indent_style = space
7
+indent_size = 2
8
+insert_final_newline = true
9
+trim_trailing_whitespace = true
10
+
11
+[*.md]
12
+max_line_length = off
13
+trim_trailing_whitespace = false

+ 46
- 0
.gitignore View File

@@ -0,0 +1,46 @@
1
+# See http://help.github.com/ignore-files/ for more about ignoring files.
2
+
3
+# compiled output
4
+/dist
5
+/tmp
6
+/out-tsc
7
+# Only exists if Bazel was run
8
+/bazel-out
9
+
10
+# dependencies
11
+/node_modules
12
+
13
+# profiling files
14
+chrome-profiler-events.json
15
+speed-measure-plugin.json
16
+
17
+# IDEs and editors
18
+/.idea
19
+.project
20
+.classpath
21
+.c9/
22
+*.launch
23
+.settings/
24
+*.sublime-workspace
25
+
26
+# IDE - VSCode
27
+.vscode/*
28
+!.vscode/settings.json
29
+!.vscode/tasks.json
30
+!.vscode/launch.json
31
+!.vscode/extensions.json
32
+.history/*
33
+
34
+# misc
35
+/.sass-cache
36
+/connect.lock
37
+/coverage
38
+/libpeerconnection.log
39
+npm-debug.log
40
+yarn-error.log
41
+testem.log
42
+/typings
43
+
44
+# System Files
45
+.DS_Store
46
+Thumbs.db

+ 27
- 0
README.md View File

@@ -0,0 +1,27 @@
1
+# Dashboard
2
+
3
+This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.1.0.
4
+
5
+## Development server
6
+
7
+Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
8
+
9
+## Code scaffolding
10
+
11
+Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
12
+
13
+## Build
14
+
15
+Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
16
+
17
+## Running unit tests
18
+
19
+Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
20
+
21
+## Running end-to-end tests
22
+
23
+Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
24
+
25
+## Further help
26
+
27
+To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

+ 130
- 0
angular.json View File

@@ -0,0 +1,130 @@
1
+{
2
+  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
3
+  "version": 1,
4
+  "newProjectRoot": "projects",
5
+  "projects": {
6
+    "dashboard": {
7
+      "projectType": "application",
8
+      "schematics": {
9
+        "@schematics/angular:component": {
10
+          "style": "scss"
11
+        }
12
+      },
13
+      "root": "",
14
+      "sourceRoot": "src",
15
+      "prefix": "app",
16
+      "architect": {
17
+        "build": {
18
+          "builder": "@angular-devkit/build-angular:browser",
19
+          "options": {
20
+            "outputPath": "dist/dashboard",
21
+            "index": "src/index.html",
22
+            "main": "src/main.ts",
23
+            "polyfills": "src/polyfills.ts",
24
+            "tsConfig": "tsconfig.app.json",
25
+            "aot": false,
26
+            "assets": [
27
+              "src/favicon.ico",
28
+              "src/assets"
29
+            ],
30
+            "styles": [
31
+              "src/styles.scss",
32
+              "node_modules/@clr/icons/clr-icons.min.css",
33
+              "node_modules/@clr/ui/clr-ui-dark.min.css"
34
+            ],
35
+            "scripts": [
36
+              "node_modules/systemjs/dist/system.js",
37
+              "node_modules/@webcomponents/custom-elements/custom-elements.min.js",
38
+              "node_modules/@clr/icons/clr-icons.min.js"
39
+            ]
40
+          },
41
+          "configurations": {
42
+            "production": {
43
+              "fileReplacements": [
44
+                {
45
+                  "replace": "src/environments/environment.ts",
46
+                  "with": "src/environments/environment.prod.ts"
47
+                }
48
+              ],
49
+              "optimization": true,
50
+              "outputHashing": "all",
51
+              "sourceMap": false,
52
+              "extractCss": true,
53
+              "namedChunks": false,
54
+              "aot": false,
55
+              "extractLicenses": true,
56
+              "vendorChunk": false,
57
+              "buildOptimizer": true,
58
+              "budgets": [
59
+                {
60
+                  "type": "initial",
61
+                  "maximumWarning": "2mb",
62
+                  "maximumError": "5mb"
63
+                }
64
+              ]
65
+            }
66
+          }
67
+        },
68
+        "serve": {
69
+          "builder": "@angular-devkit/build-angular:dev-server",
70
+          "options": {
71
+            "browserTarget": "dashboard:build"
72
+          },
73
+          "configurations": {
74
+            "production": {
75
+              "browserTarget": "dashboard:build:production"
76
+            }
77
+          }
78
+        },
79
+        "extract-i18n": {
80
+          "builder": "@angular-devkit/build-angular:extract-i18n",
81
+          "options": {
82
+            "browserTarget": "dashboard:build"
83
+          }
84
+        },
85
+        "test": {
86
+          "builder": "@angular-devkit/build-angular:karma",
87
+          "options": {
88
+            "main": "src/test.ts",
89
+            "polyfills": "src/polyfills.ts",
90
+            "tsConfig": "tsconfig.spec.json",
91
+            "karmaConfig": "karma.conf.js",
92
+            "assets": [
93
+              "src/favicon.ico",
94
+              "src/assets"
95
+            ],
96
+            "styles": [
97
+              "src/styles.scss"
98
+            ],
99
+            "scripts": []
100
+          }
101
+        },
102
+        "lint": {
103
+          "builder": "@angular-devkit/build-angular:tslint",
104
+          "options": {
105
+            "tsConfig": [
106
+              "tsconfig.app.json",
107
+              "tsconfig.spec.json",
108
+              "e2e/tsconfig.json"
109
+            ],
110
+            "exclude": [
111
+              "**/node_modules/**"
112
+            ]
113
+          }
114
+        },
115
+        "e2e": {
116
+          "builder": "@angular-devkit/build-angular:protractor",
117
+          "options": {
118
+            "protractorConfig": "e2e/protractor.conf.js",
119
+            "devServerTarget": "dashboard:serve"
120
+          },
121
+          "configurations": {
122
+            "production": {
123
+              "devServerTarget": "dashboard:serve:production"
124
+            }
125
+          }
126
+        }
127
+      }
128
+    }},
129
+  "defaultProject": "dashboard"
130
+}

+ 12
- 0
browserslist View File

@@ -0,0 +1,12 @@
1
+# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
2
+# For additional information regarding the format and rule options, please see:
3
+# https://github.com/browserslist/browserslist#queries
4
+
5
+# You can see what browsers were selected by your queries by running:
6
+#   npx browserslist
7
+
8
+> 0.5%
9
+last 2 versions
10
+Firefox ESR
11
+not dead
12
+not IE 9-11 # For IE 9-11 support, remove 'not'.

+ 32
- 0
e2e/protractor.conf.js View File

@@ -0,0 +1,32 @@
1
+// @ts-check
2
+// Protractor configuration file, see link for more information
3
+// https://github.com/angular/protractor/blob/master/lib/config.ts
4
+
5
+const { SpecReporter } = require('jasmine-spec-reporter');
6
+
7
+/**
8
+ * @type { import("protractor").Config }
9
+ */
10
+exports.config = {
11
+  allScriptsTimeout: 11000,
12
+  specs: [
13
+    './src/**/*.e2e-spec.ts'
14
+  ],
15
+  capabilities: {
16
+    'browserName': 'chrome'
17
+  },
18
+  directConnect: true,
19
+  baseUrl: 'http://localhost:4200/',
20
+  framework: 'jasmine',
21
+  jasmineNodeOpts: {
22
+    showColors: true,
23
+    defaultTimeoutInterval: 30000,
24
+    print: function() {}
25
+  },
26
+  onPrepare() {
27
+    require('ts-node').register({
28
+      project: require('path').join(__dirname, './tsconfig.json')
29
+    });
30
+    jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
31
+  }
32
+};

+ 23
- 0
e2e/src/app.e2e-spec.ts View File

@@ -0,0 +1,23 @@
1
+import { AppPage } from './app.po';
2
+import { browser, logging } from 'protractor';
3
+
4
+describe('workspace-project App', () => {
5
+  let page: AppPage;
6
+
7
+  beforeEach(() => {
8
+    page = new AppPage();
9
+  });
10
+
11
+  it('should display welcome message', () => {
12
+    page.navigateTo();
13
+    expect(page.getTitleText()).toEqual('Welcome to dashboard!');
14
+  });
15
+
16
+  afterEach(async () => {
17
+    // Assert that there are no errors emitted from the browser
18
+    const logs = await browser.manage().logs().get(logging.Type.BROWSER);
19
+    expect(logs).not.toContain(jasmine.objectContaining({
20
+      level: logging.Level.SEVERE,
21
+    } as logging.Entry));
22
+  });
23
+});

+ 11
- 0
e2e/src/app.po.ts View File

@@ -0,0 +1,11 @@
1
+import { browser, by, element } from 'protractor';
2
+
3
+export class AppPage {
4
+  navigateTo() {
5
+    return browser.get(browser.baseUrl) as Promise<any>;
6
+  }
7
+
8
+  getTitleText() {
9
+    return element(by.css('app-root h1')).getText() as Promise<string>;
10
+  }
11
+}

+ 13
- 0
e2e/tsconfig.json View File

@@ -0,0 +1,13 @@
1
+{
2
+  "extends": "../tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "../out-tsc/e2e",
5
+    "module": "commonjs",
6
+    "target": "es5",
7
+    "types": [
8
+      "jasmine",
9
+      "jasminewd2",
10
+      "node"
11
+    ]
12
+  }
13
+}

+ 32
- 0
karma.conf.js View File

@@ -0,0 +1,32 @@
1
+// Karma configuration file, see link for more information
2
+// https://karma-runner.github.io/1.0/config/configuration-file.html
3
+
4
+module.exports = function (config) {
5
+  config.set({
6
+    basePath: '',
7
+    frameworks: ['jasmine', '@angular-devkit/build-angular'],
8
+    plugins: [
9
+      require('karma-jasmine'),
10
+      require('karma-chrome-launcher'),
11
+      require('karma-jasmine-html-reporter'),
12
+      require('karma-coverage-istanbul-reporter'),
13
+      require('@angular-devkit/build-angular/plugins/karma')
14
+    ],
15
+    client: {
16
+      clearContext: false // leave Jasmine Spec Runner output visible in browser
17
+    },
18
+    coverageIstanbulReporter: {
19
+      dir: require('path').join(__dirname, './coverage/dashboard'),
20
+      reports: ['html', 'lcovonly', 'text-summary'],
21
+      fixWebpackSourcePaths: true
22
+    },
23
+    reporters: ['progress', 'kjhtml'],
24
+    port: 9876,
25
+    colors: true,
26
+    logLevel: config.LOG_INFO,
27
+    autoWatch: true,
28
+    browsers: ['Chrome'],
29
+    singleRun: false,
30
+    restartOnFileChange: true
31
+  });
32
+};

+ 62
- 0
package.json View File

@@ -0,0 +1,62 @@
1
+{
2
+  "name": "dashboard",
3
+  "version": "0.0.0",
4
+  "scripts": {
5
+    "ng": "ng",
6
+    "start": "ng serve --aot=false --optimization=false --proxy-config proxy.conf.json",
7
+    "build": "ng build --prod --aot=false --optimization=false --build-optimizer=false",
8
+    "test": "ng test",
9
+    "lint": "ng lint",
10
+    "e2e": "ng e2e",
11
+    "update-frontblock": "rm -rf node_modules/frontblock*; npm install"
12
+  },
13
+  "private": true,
14
+  "dependencies": {
15
+    "@angular/animations": "~8.2.1",
16
+    "@angular/common": "~8.2.1",
17
+    "@angular/compiler": "~8.2.1",
18
+    "@angular/core": "~8.2.1",
19
+    "@angular/forms": "~8.2.1",
20
+    "@angular/platform-browser": "~8.2.1",
21
+    "@angular/platform-browser-dynamic": "~8.2.1",
22
+    "@angular/router": "~8.2.1",
23
+    "@clr/angular": "^2.1.1",
24
+    "@clr/icons": "^2.1.1",
25
+    "@clr/ui": "^2.1.1",
26
+    "@webcomponents/custom-elements": "^1.0.0",
27
+    "btc-hdkey": "0.0.17",
28
+    "coinselect": "^3.1.11",
29
+    "frontblock": "latest",
30
+    "frontblock-generic": "^0.22.0",
31
+    "key-file-storage": "^2.2.4",
32
+    "node-fetch": "^2.6.0",
33
+    "rxjs": "~6.5.2",
34
+    "stream": "0.0.2",
35
+    "systemjs": "^0.21.3",
36
+    "tslib": "^1.9.0",
37
+    "uuid": "^3.3.2",
38
+    "zone.js": "~0.10.1"
39
+  },
40
+  "devDependencies": {
41
+    "@angular-devkit/build-angular": "~0.802.1",
42
+    "@angular/cli": "^8.2.1",
43
+    "@angular/compiler-cli": "~8.2.1",
44
+    "@angular/language-service": "~8.2.1",
45
+    "@types/jasmine": "~3.4.0",
46
+    "@types/jasminewd2": "~2.0.3",
47
+    "@types/node": "^12.7.1",
48
+    "@types/systemjs": "^0.20.6",
49
+    "codelyzer": "^5.0.0",
50
+    "jasmine-core": "~3.4.0",
51
+    "jasmine-spec-reporter": "~4.2.1",
52
+    "karma": "~4.2.0",
53
+    "karma-chrome-launcher": "~3.0.0",
54
+    "karma-coverage-istanbul-reporter": "~2.1.0",
55
+    "karma-jasmine": "~2.0.1",
56
+    "karma-jasmine-html-reporter": "^1.4.0",
57
+    "protractor": "~5.4.0",
58
+    "ts-node": "~8.3.0",
59
+    "tslint": "~5.18.0",
60
+    "typescript": "~3.5.3"
61
+  }
62
+}

+ 9
- 0
proxy.conf.json View File

@@ -0,0 +1,9 @@
1
+{
2
+  "/widgets-repo/*": {
3
+    "target": "http://localhost:4201",
4
+    "secure": false,
5
+    "pathRewrite": {
6
+      "^/widgets-repo": ""
7
+    }
8
+  }
9
+}

+ 93
- 0
src/app/ApiClient/apiclient-consumptions.component.ts View File

@@ -0,0 +1,93 @@
1
+import { Component, OnInit, isDevMode } from '@angular/core';
2
+import { SubscriptionResponse, parseResponse, SuccessResponse, Coin } from 'frontblock-generic/Types.js';
3
+import { default as uuid } from 'uuid/v4'
4
+
5
+declare const fb
6
+ 
7
+@Component({
8
+    selector: 'consumers',
9
+    template: `
10
+  <div class="clr-row">
11
+    <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
12
+        <div class="card-header">
13
+        Consumers
14
+        </div>
15
+        <clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity">
16
+            <clr-alert-item>
17
+                <span class="alert-text">
18
+                    {{entry.message}}
19
+                </span>
20
+            </clr-alert-item>
21
+        </clr-alert>
22
+
23
+        <div *ngIf="loading" class="card-block">
24
+        <span class="spinner spinner-inline"></span>
25
+        </div>
26
+        <div *ngIf="entries.length !== 0 && !loading" class="card-block limit-height">
27
+            <div class="card-text limit-height">
28
+                <clr-datagrid>
29
+                    <clr-dg-column>UUID</clr-dg-column>
30
+                    <clr-dg-column>Subscription UUID</clr-dg-column>
31
+                    <clr-dg-column>Expiry</clr-dg-column>
32
+                    <clr-dg-column>Creation</clr-dg-column>
33
+
34
+                    <clr-dg-row *clrDgItems="let line of entries">
35
+                        <clr-dg-cell>{{line.uid}}</clr-dg-cell>
36
+                        <clr-dg-cell>{{line.message}}</clr-dg-cell>
37
+                        <clr-dg-cell>{{line.expiry | date}}</clr-dg-cell>
38
+                        <clr-dg-cell>{{line.created | date}}</clr-dg-cell>
39
+                        
40
+                        <clr-dg-row-detail *clrIfExpanded>
41
+                            <button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Quit</button>
42
+                        </clr-dg-row-detail>
43
+                    </clr-dg-row>
44
+                    <clr-dg-footer>
45
+                        <clr-dg-pagination #pagination [clrDgPageSize]="10">
46
+                            <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
47
+                            {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
48
+                            of {{pagination.totalItems}} users
49
+                        </clr-dg-pagination>
50
+                    </clr-dg-footer>
51
+                </clr-datagrid>
52
+            </div>
53
+        </div>
54
+        <div class="card-footer">
55
+            <button *ngIf="!loading" class="btn btn-sm btn-link" (click)="load()">{{actionName}}</button>
56
+        </div>
57
+    </div>
58
+  `
59
+})
60
+export class ApiclientConsumptionComponent implements OnInit {
61
+    loading: boolean = false
62
+    actionName: string = "load"
63
+    entries: SubscriptionResponse[] = []
64
+    alerts: {
65
+        severity: "danger" | "warning" | "success"
66
+        message: string
67
+    }[] = []
68
+
69
+    constructor() { }
70
+
71
+    ngOnInit() { }
72
+
73
+    async load() {
74
+        this.loading = true
75
+        this.entries = await fb.ApiClient.getConsumptions()
76
+        if (this.entries.length === 0) {
77
+            this.alerts.push({ severity: "warning", message: "0 results were returned" })
78
+        }
79
+        this.loading = false
80
+        this.actionName = "refresh"
81
+    }
82
+
83
+    async quit(uid: string) {
84
+        const r = await fb.ApiClient.quit(uid)
85
+        const res = parseResponse(r)
86
+        if (res instanceof SuccessResponse) {
87
+            this.alerts.push({ severity: "success", message: "Quit consuming " + uid })
88
+            this.load()
89
+        } else {
90
+            this.alerts.push({ severity: "danger", message: "Error " + res.message })
91
+        }
92
+    }
93
+}

+ 132
- 0
src/app/ApiClient/apiclient-settings-form.component.ts View File

@@ -0,0 +1,132 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { FrontblockApiConf } from 'frontblock';
3
+import { isDevMode } from '@angular/core';
4
+
5
+declare const fb
6
+
7
+@Component({
8
+    selector: 'settings',
9
+    template: `
10
+    <div class="clr-row">
11
+        <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
12
+            <div class="card-header">
13
+            Settings
14
+            </div>
15
+            <div class="card-block">
16
+                <div class="card-text">
17
+                    <form clrForm clrLayout="horizontal">
18
+                        <div class="clr-row clr-form-control ng-star-inserted">
19
+                            <label class="clr-col-12 clr-col-md-4 clr-control-label">testnet</label>
20
+                            <div class="clr-col-12 clr-col-md-8">
21
+                                <input type="checkbox" [(ngModel)]="testnet"  (change)="updateTestnet($event)" name="testnetToggle" clrToggle  />
22
+                            </div>
23
+                        </div>
24
+                        
25
+                        <clr-input-container *ngIf="!testnet">
26
+                            <label class="clr-col-12 clr-col-md-4">API key</label>
27
+                            <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="apiKey" required />
28
+                        </clr-input-container>
29
+                    
30
+                        <clr-input-container *ngIf="advanced" class="clr-row">
31
+                            <label class="clr-col-12 clr-col-md-4">API address</label>
32
+                            <input class="clr-col-12 clr-col-md-8" type="text" [(ngModel)]="data.apiHost" (change)="updatePort($event)" clrInput name="apiHost" required />
33
+                            <clr-control-error>This field is required!</clr-control-error>
34
+                        </clr-input-container>
35
+
36
+                        <div class="clr-row clr-form-control ng-star-inserted" *ngIf="advanced">
37
+                            <label class="clr-col-12 clr-col-md-4 clr-control-label">Use TLS</label>
38
+                            <div class="clr-col-12 clr-col-md-8">
39
+                                <input [(ngModel)]="data.tls" type="checkbox" clrCheckbox value="tls" name="options" />
40
+                            </div>
41
+                        </div>
42
+                        
43
+                        <clr-input-container class="clr-row" *ngIf="advanced">
44
+                            <label class="clr-col-12 clr-col-md-4">Port</label>
45
+                            <input class="clr-col-12 clr-col-md-8" [(ngModel)]="data.apiPort" clrInput type="number" name="lastName" required />
46
+                            <clr-control-error>Valid range 1024 - 65536 
47
+                            </clr-control-error>
48
+                        </clr-input-container>
49
+                    </form>
50
+                </div>
51
+            </div>
52
+            <div class="card-footer">
53
+                <button *ngIf="!saving" class="btn btn-success-outline" (click)="save()">
54
+                <span>Save</span>
55
+                </button>
56
+
57
+                <button *ngIf="saving" class="btn btn-disabled" (click)="save()" disabled>
58
+                <span class="spinner spinner-inline"></span>
59
+                </button>
60
+                <button *ngIf="!advanced" class="btn btn-sm btn-link" (click)="setAdvanced()">Advanced</button>
61
+            </div>
62
+        </div>
63
+    </div>
64
+  `
65
+})
66
+export class ApiclientFormComponent implements OnInit {
67
+    testnet: boolean = true
68
+    saving: boolean = false
69
+    advanced: boolean = false
70
+    data: FrontblockApiConf = {
71
+        apiHost: "api.testnet.frontblock.me",
72
+        apiPort: 10001,
73
+        tls: false,
74
+        apiKey: ""
75
+    }
76
+
77
+    constructor() { }
78
+
79
+    ngOnInit() { }
80
+
81
+    setAdvanced() { this.advanced = true }
82
+
83
+    checkData(): boolean {
84
+        return (
85
+            typeof this.data.apiPort === "number" &&
86
+            typeof this.data.apiHost === "string" &&
87
+            typeof this.data.tls === "boolean" &&
88
+            typeof this.data.apiKey === "string"
89
+        )
90
+    }
91
+
92
+    updateTestnet() {
93
+        if (this.testnet) {
94
+            this.data.apiHost = "api.testnet.frontblock.me"
95
+            this.data.apiPort = 10001
96
+        } else {
97
+            this.data.apiHost = "api.frontblock.me"
98
+            this.data.apiPort = 10000
99
+        }
100
+    }
101
+
102
+    updatePort(obj) {
103
+        switch (this.data.apiHost) {
104
+            case "api.testnet.frontblock.me":
105
+                this.data.apiPort = 10001
106
+                break;
107
+            case "api.frontblock.me":
108
+                this.data.apiPort = 10000
109
+                break;
110
+
111
+            default:
112
+                console.warn("A non-standard api host was chosen: " + this.data.apiPort)
113
+                break;
114
+        }
115
+        console.log(this.data)
116
+    }
117
+
118
+    async save() {
119
+        this.saving = true;
120
+        if (typeof this.data.apiPort === "string")
121
+            this.data.apiPort = parseInt(this.data.apiPort)
122
+
123
+        if (this.checkData()) {
124
+            const conf = await fb.ApiClient.setConfig(this.data)
125
+            console.log(conf)
126
+        } else {
127
+            //show error in gui
128
+            console.error("bad data :(")
129
+        }
130
+        this.saving = false
131
+    }
132
+}

+ 103
- 0
src/app/ApiClient/apiclient-subscriptions.component.ts View File

@@ -0,0 +1,103 @@
1
+import { Component, OnInit, isDevMode } from '@angular/core';
2
+import { SubscriptionResponse, SuccessResponse, parseResponse, Coin } from 'frontblock-generic/Types.js';
3
+import { default as uuid } from 'uuid/v4'
4
+declare const fb
5
+
6
+@Component({
7
+    selector: 'subscriptions',
8
+    template: `
9
+    <div class="clr-row">
10
+        <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
11
+            <div class="card-header">
12
+            Subscriptions
13
+            </div>
14
+            <clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity">
15
+                <clr-alert-item>
16
+                    <span class="alert-text">
17
+                        {{entry.message}}
18
+                    </span>
19
+                </clr-alert-item>
20
+            </clr-alert>
21
+        
22
+            <div *ngIf="loading" class="card-block">
23
+            <span class="spinner spinner-inline"></span>
24
+            </div>
25
+            <div *ngIf="entries.length !== 0 && !loading" class="card-block">
26
+                <div class="card-text">
27
+                    <clr-datagrid>
28
+                        <clr-dg-column>UUID</clr-dg-column>
29
+                    <!--
30
+                        <clr-dg-column>Currency</clr-dg-column>
31
+                        <clr-dg-column>Address</clr-dg-column>
32
+                        <clr-dg-column>Memo</clr-dg-column>
33
+                        <clr-dg-column>Expiry</clr-dg-column>
34
+                        <clr-dg-column>Creation</clr-dg-column>
35
+                    -->
36
+        
37
+                        <clr-dg-row *clrDgItems="let line of entries">
38
+                            <clr-dg-cell>{{line.uid}}</clr-dg-cell>
39
+                        <!-- 
40
+                            <clr-dg-cell>{{line.currency}}</clr-dg-cell> 
41
+                            <clr-dg-cell>{{line.address}}</clr-dg-cell> 
42
+                            <clr-dg-cell>{{line.memo}}</clr-dg-cell> 
43
+                            <clr-dg-cell>{{line.expiry | date}}</clr-dg-cell> 
44
+                            <clr-dg-cell>{{line.created | date}}</clr-dg-cell> 
45
+                        -->
46
+                            
47
+                            <clr-dg-row-detail *clrIfExpanded>
48
+                                <button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Unsubscribe</button>
49
+                            </clr-dg-row-detail>
50
+                        </clr-dg-row>
51
+                        <clr-dg-footer>
52
+                            <clr-dg-pagination #pagination [clrDgPageSize]="10">
53
+                                <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
54
+                                {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
55
+                                of {{pagination.totalItems}} users
56
+                            </clr-dg-pagination>
57
+                        </clr-dg-footer>
58
+                    </clr-datagrid>
59
+                </div>
60
+            </div>
61
+            <div class="card-footer">
62
+                <button *ngIf="!loading" class="btn btn-sm btn-link" (click)="load()">{{actionName}}</button>
63
+        
64
+            </div>
65
+        </div>
66
+    </div>
67
+`
68
+})
69
+export class ApiclientSubscriptionComponent implements OnInit {
70
+    loading: boolean = false
71
+    actionName: string = "load"
72
+    entries: (SubscriptionResponse )[] = []
73
+    alerts: {
74
+        severity: "danger" | "warning" | "success"
75
+        message: string
76
+    }[] = []
77
+
78
+    constructor() { }
79
+
80
+    ngOnInit() { }
81
+
82
+    async load() {
83
+        this.loading = true
84
+        this.entries = await fb.ApiClient.getSubscriptions()
85
+        if (this.entries.length === 0) {
86
+            this.alerts.push({ severity: "warning", message: "0 results were returned" })
87
+        }
88
+        this.loading = false
89
+        this.actionName = "refresh"
90
+    }
91
+
92
+    async quit(uid: string) {
93
+        const r = await fb.ApiClient.unsubscribe(uid)
94
+        const res = parseResponse(r)
95
+        if (res instanceof SuccessResponse) {
96
+            this.alerts.push({ severity: "success", message: "Stopped subscription " + uid })
97
+            this.load()
98
+        } else {
99
+            this.alerts.push({ severity: "danger", message: "Error " + res.message })
100
+        }
101
+    }
102
+
103
+}

+ 51
- 0
src/app/ApiClient/module.ts View File

@@ -0,0 +1,51 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { RouterModule } from '@angular/router';
4
+
5
+import { ApiclientFormComponent } from './apiclient-settings-form.component';
6
+import { ApiclientConsumptionComponent } from './apiclient-consumptions.component';
7
+import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component';
8
+
9
+import { FrontendPlugin, SidebarEntry, SidebarEntries } from 'frontblock-generic/Plugin';
10
+import { ClarityModule } from '@clr/angular';
11
+import { FormsModule } from '@angular/forms';
12
+
13
+
14
+@NgModule({
15
+  imports: [
16
+    FormsModule,
17
+    ClarityModule,
18
+
19
+    CommonModule, 
20
+    RouterModule.forChild([
21
+      {path: "settings", component: ApiclientFormComponent},
22
+      {path: "subscriptions", component: ApiclientSubscriptionComponent},
23
+      {path: "consumers", component: ApiclientConsumptionComponent},
24
+    ]),
25
+  ],
26
+  exports: [RouterModule],
27
+  declarations: [
28
+    ApiclientFormComponent,
29
+    ApiclientConsumptionComponent,
30
+    ApiclientSubscriptionComponent
31
+  ]
32
+})
33
+export class PluginModule implements FrontendPlugin{
34
+  getSidebarEntry(): SidebarEntries {
35
+    return {
36
+      icon: "key",
37
+      parentRoute: "apiclient",
38
+      text: "Api client",
39
+      links: [{
40
+        route: "settings",
41
+        text: "Settings"
42
+      },{
43
+        route: "consumers",
44
+        text: "Consumers"
45
+      },{
46
+        route: "subscriptions",
47
+        text: "Subscriptions"
48
+      },]
49
+    }
50
+  }
51
+}

+ 47
- 0
src/app/PluginManager/debug.component.ts View File

@@ -0,0 +1,47 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { SidebarEntry } from 'frontblock-generic/Plugin';
3
+declare const fb
4
+@Component({
5
+  selector: 'debug', //!!!!
6
+  template: `
7
+  <div class="clr-row">
8
+    <div class="clr-col">
9
+        <div class="card">
10
+            <div class="card-header">
11
+                DEBUG
12
+            </div>
13
+            <div class="card-block">
14
+                <div class="card-title">
15
+                    SETUP
16
+                </div>
17
+                <div class="card-text">
18
+                  <p>
19
+                    Press this button to install all plugins.
20
+                    <br>
21
+                    Once the installation is done the page will refresh automatically
22
+                  </p>
23
+                </div>
24
+            </div>
25
+            <div class="card-footer">
26
+              <button class="btn btn-warning-outline" onclick="setup()">SETUP</button>
27
+            </div>
28
+        </div>
29
+    </div>
30
+  </div>
31
+  `
32
+})
33
+export class PluginmanagerDEBUG implements OnInit {
34
+
35
+  constructor() { }
36
+
37
+  ngOnInit() { }
38
+
39
+}
40
+
41
+export const sidebarEntry: SidebarEntry = {
42
+  icon: "wrench",
43
+  route: "dev/pluginmanager",
44
+  text: "DEV Plugin manager",
45
+}
46
+
47
+

+ 45
- 0
src/app/PluginManager/module.ts View File

@@ -0,0 +1,45 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { RouterModule } from '@angular/router';
4
+
5
+import { PluginmanagerDEBUG } from './debug.component';
6
+import { PluginsComponent } from './plugins.component';
7
+
8
+import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
9
+import { ClarityModule } from '@clr/angular';
10
+import { FormsModule } from '@angular/forms';
11
+
12
+
13
+@NgModule({
14
+  imports: [
15
+    FormsModule,
16
+    ClarityModule,
17
+
18
+    CommonModule, 
19
+    RouterModule.forChild([
20
+      {path: "debug", component: PluginmanagerDEBUG},
21
+      {path: "plugins", component: PluginsComponent}
22
+    ]),
23
+  ],
24
+  exports: [RouterModule],
25
+  declarations: [
26
+    PluginmanagerDEBUG,
27
+    PluginsComponent
28
+  ]
29
+})
30
+export class PluginModule implements FrontendPlugin{
31
+  getSidebarEntry(): SidebarEntries {
32
+    return {
33
+      icon: "plugin",
34
+      text: "Plugin manager",
35
+      parentRoute: "pluginmanager",
36
+      links: [{
37
+        route: "debug",
38
+        text: "DEBUG"    
39
+      },{
40
+        route: "plugins",
41
+        text: "Plugins"    
42
+      }]
43
+    }
44
+  }
45
+}

+ 156
- 0
src/app/PluginManager/plugins.component.ts View File

@@ -0,0 +1,156 @@
1
+import { Component, OnInit, isDevMode } from '@angular/core';
2
+declare const fb
3
+@Component({
4
+    selector: 'plugins', //!!!!
5
+    template: `
6
+  <clr-modal [(clrModalOpen)]="updatepending">
7
+    <h3 class="modal-title">Confirm installation</h3>
8
+    <div class="modal-body">
9
+        <p>{{updateCandidate}} But not much to say...</p>
10
+    </div>
11
+    <div class="modal-footer">
12
+        <button class="btn btn-icon btn-success" (click)="download(updateCandidate)"><clr-icon shape="check"></clr-icon></button>
13
+        <button class="btn btn-icon btn-danger" (click)="cancelModal()"><clr-icon shape="times"></clr-icon></button>
14
+    </div>
15
+  </clr-modal>
16
+
17
+  <div class="clr-row">
18
+    <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
19
+        <div class="card-header">
20
+            Plugins
21
+        </div>
22
+        <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
23
+            <clr-alert-item>
24
+                <span class="alert-text">
25
+                    {{line.message}}
26
+                </span>
27
+            </clr-alert-item>
28
+        </clr-alert>
29
+    
30
+        <div *ngIf="loading" class="card-block">
31
+        <span class="spinner spinner-inline"></span>
32
+        </div>
33
+        <div *ngIf="plugins.length !== 0 && !loading" class="card-block">
34
+            <div class="card-text">
35
+                <clr-datagrid>
36
+                    <clr-dg-column>Name</clr-dg-column>
37
+                    <clr-dg-column>Available</clr-dg-column>
38
+                    <clr-dg-column>Installed</clr-dg-column>
39
+                    <clr-dg-column>Status</clr-dg-column>
40
+    
41
+                    <clr-dg-row *clrDgItems="let line of plugins">
42
+                        <clr-dg-cell>{{line.name}}</clr-dg-cell>
43
+                        <clr-dg-cell>
44
+                            <span *ngIf="line.installed != line.available" class="label info">{{line.available}}</span>
45
+                            <span *ngIf="line.installed == line.available">{{line.available}}</span>
46
+                        </clr-dg-cell>
47
+                        <clr-dg-cell>{{line.installed}}</clr-dg-cell>
48
+                        <clr-dg-cell>{{line.status}}</clr-dg-cell>
49
+                        
50
+                        <clr-dg-row-detail *clrIfExpanded>
51
+                            <button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-success-outline" (click)="start(line.name)"><clr-icon shape="play"></clr-icon></button>
52
+                            <button *ngIf="line.status === 'Running'" class="btn btn-icon btn-warning-outline" (click)="stop(line.name)"><clr-icon shape="pause"></clr-icon></button>
53
+                            <button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-danger-outline" (click)="delete(line.name)"><clr-icon shape="trash"></clr-icon></button>
54
+                            <button *ngIf="line.status === 'Available'" class="btn btn-icon btn-info-outline" (click)="showConfirmation(line.name)"><clr-icon shape="download"></clr-icon></button>
55
+                            <button *ngIf="line.available !== line.installed && line.status === 'Stopped'" class="btn btn-icon btn-outline " (click)="showConfirmation(line.name)">
56
+                                <clr-icon shape="sync"></clr-icon>
57
+                            </button>                        
58
+                        </clr-dg-row-detail>
59
+                    </clr-dg-row>
60
+                    <clr-dg-footer>
61
+                        <clr-dg-pagination #pagination [clrDgPageSize]="10">
62
+                            <clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">per page</clr-dg-page-size>
63
+                            {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
64
+                            of {{pagination.totalItems}}
65
+                        </clr-dg-pagination>
66
+                    </clr-dg-footer>
67
+                </clr-datagrid>
68
+            </div>
69
+        </div>
70
+        <div class="card-footer">
71
+            <button *ngIf="!reloading" class="btn btn-sm btn-link" (click)="reload()">Check</button>
72
+    
73
+        </div>
74
+    </div>
75
+    </div>
76
+  `
77
+})
78
+export class PluginsComponent implements OnInit {
79
+    plugins = [{
80
+        name: "test",
81
+        available: "0.0.1",
82
+        installed: "0.0.1",
83
+        status: "Running"
84
+    }, {
85
+        name: "test II: Return of test",
86
+        available: "0.0.1",
87
+        installed: "0.0.1",
88
+        status: "Stopped"
89
+    }, {
90
+        name: "test 3",
91
+        available: "0.0.2",
92
+        installed: "",
93
+        status: "Available"
94
+    }, {
95
+        name: "test IV: minor bump",
96
+        available: "0.9.27",
97
+        installed: "0.0.11",
98
+        status: "Stopped"
99
+    },]
100
+
101
+    reloading = false
102
+    updatepending = false
103
+    updateCandidate = ""
104
+
105
+    constructor() { }
106
+
107
+    start(name) {
108
+        this.plugins.find((el) => el.name === name).status = "Running"
109
+    }
110
+
111
+    stop(name) {
112
+        this.plugins.find((el) => el.name === name).status = "Stopped"
113
+    }
114
+
115
+    delete(name) {
116
+        let plugin = this.plugins.find((el) => el.name === name)
117
+        plugin.status = "Available"
118
+        plugin.installed = ""
119
+    }
120
+
121
+    showConfirmation(name) {
122
+        this.updateCandidate = name
123
+        this.updatepending = !this.updatepending
124
+    }
125
+
126
+    download(name) {
127
+        let plugin = this.plugins.find((el) => el.name === name)
128
+        plugin.status = "Stopped"
129
+        plugin.installed = plugin.available
130
+        this.updatepending = !this.updatepending
131
+    }
132
+
133
+    cancelModal() {
134
+        this.updateCandidate = ""
135
+        this.updatepending = !this.updatepending
136
+    }
137
+
138
+    async reload(){
139
+        this.reloading = true
140
+        if(isDevMode()){
141
+            await new Promise((resolve, reject) => {
142
+                setTimeout(resolve, 1000)
143
+            })
144
+            this.plugins = this.plugins.map(plugin => {
145
+                const parts = plugin.available.split('.')
146
+                plugin.available = parts[0]+"."+parts[1]+"."+(parseInt(parts[2])+1)
147
+                return plugin
148
+            })
149
+            this.reloading = false
150
+        }
151
+    }
152
+
153
+    ngOnInit() { }
154
+
155
+}
156
+

+ 174
- 0
src/app/Wallet/btc/address-viewer.component.ts View File

@@ -0,0 +1,174 @@
1
+
2
+
3
+import { Component, OnInit, isDevMode } from '@angular/core';
4
+import { PluginsComponent } from './btc.component';
5
+import { ClrDatagridStringFilterInterface, ClrDatagridNumericFilterInterface, ClrDropdownModule } from '@clr/angular';
6
+declare const fb
7
+@Component({
8
+    selector: 'address-viewer', //!!!!
9
+    template: `
10
+    <div class="clr-row">
11
+        <div *ngIf="regularAddresses.length != 0" class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-6">
12
+        <div class="card">    
13
+            <div class="card-header">
14
+                Addresses
15
+            </div>
16
+            <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
17
+                <clr-alert-item>
18
+                    <span class="alert-text">
19
+                        {{line.message}}
20
+                    </span>
21
+                </clr-alert-item>
22
+            </clr-alert>
23
+
24
+            <clr-datagrid class="datagrid-compact ">
25
+                <clr-dg-column>Address</clr-dg-column>
26
+                <clr-dg-column>
27
+                    Balance
28
+                </clr-dg-column>
29
+                <clr-dg-column>
30
+                    Pending
31
+                </clr-dg-column>
32
+                <clr-dg-column>
33
+                    Estimate total
34
+                    <clr-dg-numeric-filter [clrDgNumericFilter]="otherBalanceFilter" [clrFilterValue]="[0, null]"></clr-dg-numeric-filter>                
35
+                </clr-dg-column>
36
+
37
+                <clr-dg-row *clrDgItems="let line of regularAddresses">
38
+                    <clr-dg-cell class="monospace" style="word-break: break-all">{{line.address}}</clr-dg-cell>
39
+                    <clr-dg-cell class="monospace">{{line.confirmed.balance | number: '1.8-18'}}</clr-dg-cell>
40
+                    <clr-dg-cell class="monospace">
41
+                        <span *ngIf="line.unconfirmed.balance != 0">
42
+                            {{line.unconfirmed.balance | number: '1.8-18'}} 
43
+                        </span>
44
+                    </clr-dg-cell>
45
+                    <clr-dg-cell class="monospace">{{line.total.balance | number: '1.8-18'}}</clr-dg-cell>
46
+
47
+                    <clr-dg-row-detail *clrIfExpanded >
48
+                        <div class="clr-row">
49
+                            <div class="clr-col-3">
50
+                                <button class="btn btn-info-outline"  (click)="parent.addrExplorer(line.address)">Explorer</button>
51
+                            </div>
52
+
53
+                            <div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
54
+                                <h6  style="margin-top: 0">Derivation </h6>
55
+                                {{parent.keys[line.address].derivation}}                                
56
+                                <h6> Public key </h6>
57
+                                {{parent.keys[line.address].publicExtendedKey}}
58
+                                <h6> Private key </h6>
59
+                                {{parent.keys[line.address].privateExtendedKey}}
60
+                            </div>
61
+                        </div>
62
+                    </clr-dg-row-detail>
63
+                </clr-dg-row>
64
+
65
+                <clr-dg-footer>
66
+                    <clr-dg-pagination #pagination2 [clrDgPageSize]="10">
67
+                        <clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
68
+                        {{pagination2.firstItem + 1}} - {{pagination2.lastItem + 1}}
69
+                        of {{pagination2.totalItems}}
70
+                    </clr-dg-pagination>
71
+                </clr-dg-footer>
72
+            </clr-datagrid>
73
+        </div>
74
+        </div>
75
+
76
+        <div *ngIf="changeAddresses.length != 0" class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-6">
77
+        <div class="card">    
78
+            <div class="card-header">
79
+                Change Addresses
80
+            </div>
81
+            <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
82
+                <clr-alert-item>
83
+                    <span class="alert-text">
84
+                        {{line.message}}
85
+                    </span>
86
+                </clr-alert-item>
87
+            </clr-alert>
88
+
89
+            <clr-datagrid class="datagrid-compact">
90
+                <clr-dg-column>Address</clr-dg-column>
91
+                <clr-dg-column>
92
+                    Balance
93
+                </clr-dg-column>
94
+                <clr-dg-column>
95
+                    Pending
96
+                </clr-dg-column>
97
+                <clr-dg-column>
98
+                    Estimate total
99
+                    <clr-dg-numeric-filter [clrDgNumericFilter]="balanceFilter" [clrFilterValue]="[0.00000001, null]"></clr-dg-numeric-filter>
100
+                </clr-dg-column>
101
+
102
+                <clr-dg-row *clrDgItems="let line of changeAddresses">
103
+                    <clr-dg-cell class="monospace" style="word-break: break-all">{{line.address}}</clr-dg-cell>
104
+                    <clr-dg-cell class="monospace">{{line.confirmed.balance | number: '1.8-18'}}</clr-dg-cell>
105
+                    <clr-dg-cell class="monospace">
106
+                        <span *ngIf="line.unconfirmed.balance != 0">
107
+                          {{line.unconfirmed.balance | number: '1.8-18'}} 
108
+                        </span>
109
+                    </clr-dg-cell>
110
+                    <clr-dg-cell class="monospace">{{line.total.balance | number: '1.8-18'}}</clr-dg-cell>
111
+
112
+                    <clr-dg-row-detail *clrIfExpanded >
113
+                        <div class="clr-row">
114
+                            <div class="clr-col-3">
115
+                                <button class="btn btn-info-outline" (click)="parent.addrExplorer(line.address)">Explorer</button>
116
+                                <p>
117
+                            </div>
118
+
119
+                            <div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
120
+                                <h6  style="margin-top: 0">Derivation </h6>
121
+                                {{parent.keys[line.address].derivation}}
122
+                                <h6> Public key </h6>
123
+                                {{parent.keys[line.address].publicExtendedKey}}
124
+                                <h6> Private key </h6>
125
+                                {{parent.keys[line.address].privateExtendedKey}}
126
+                            </div>
127
+                        </div>
128
+                    </clr-dg-row-detail>
129
+                </clr-dg-row>
130
+
131
+                <clr-dg-footer>
132
+                    <clr-dg-pagination #pagination [clrDgPageSize]="10">
133
+                        <clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
134
+                        {{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
135
+                        of {{pagination.totalItems}}
136
+                    </clr-dg-pagination>
137
+                </clr-dg-footer>
138
+            </clr-datagrid>
139
+        </div>
140
+        </div>
141
+    </div>
142
+  `
143
+})
144
+export class AddressViewerComponent implements OnInit{    
145
+    balanceFilter = new BalanceFilter()
146
+    otherBalanceFilter = new BalanceFilter()
147
+    regularAddresses = []
148
+    changeAddresses = []
149
+    keys = {}
150
+    parent:PluginsComponent
151
+    ngOnInit() { }
152
+
153
+    setParent(parent:PluginsComponent){
154
+        this.parent = parent
155
+    }
156
+
157
+    setRegularAddresses(addresses){
158
+        this.regularAddresses = addresses
159
+    }
160
+
161
+    setChangeAddresses(addresses){
162
+        this.changeAddresses = addresses
163
+    }    
164
+}
165
+
166
+class BalanceFilter implements ClrDatagridNumericFilterInterface<any>{
167
+    accepts(item: any, low: number, high: number): boolean {
168
+        high = parseFloat(""+high)
169
+        low = parseFloat(""+low)
170
+        const balance = (parseFloat(item.unconfirmed.balance) + parseFloat(item.confirmed.balance))
171
+        return (Number.isNaN(low)  || balance >= low) 
172
+            && (Number.isNaN(high) || balance <= high)
173
+    }
174
+}

+ 566
- 0
src/app/Wallet/btc/btc.component.ts View File

@@ -0,0 +1,566 @@
1
+import { Component, OnInit, isDevMode, ViewChild, AfterViewInit } from '@angular/core';
2
+import { WalletGeneratorComponent } from './wallet-generator.component';
3
+import { WalletPickerComponent } from './wallet-picker.component';
4
+import { HDKey } from "btc-hdkey"
5
+import { AddressViewerComponent } from './address-viewer.component';
6
+const fetch = require("node-fetch")
7
+const coinselect = require('coinselect/accumulative')
8
+import { TransactionBuilder, Signer } from 'bitcoinjs-lib'
9
+import { networks } from 'bitcoinjs-lib'
10
+let feeRate = 55 // satoshis per byte
11
+
12
+const hdkey = new HDKey()
13
+declare const fb
14
+@Component({
15
+    selector: 'plugins', //!!!!
16
+    template: `
17
+
18
+<wallet-generator></wallet-generator>
19
+
20
+<!-- loading -->
21
+<clr-modal  [clrModalSize]="'md'" [(clrModalOpen)]="loading">
22
+        
23
+    <div class="modal-body ">
24
+        <span class="spinner spinner-inverse spinner-md">
25
+            Loading...
26
+        </span> 
27
+        <h3>Checking addresses</h3> <br>
28
+        Checking the first {{loadingN*115}} addresses. <br>
29
+        Found addresses containing balance: {{regaddresses.length + changeaddresses.length}}
30
+    </div>
31
+</clr-modal>
32
+<!-- / loading -->
33
+
34
+<!-- row 0 -->
35
+<div *ngIf="mode.net == 'testnet'" class="clr-all-12">
36
+    <div class="card">
37
+                            
38
+        <div class="card-header">
39
+            Faucet
40
+        </div>
41
+        <div class="card-block" >
42
+            <button class="btn btn-icon btn-info-outline" onclick="window.open('https://testnet-faucet.mempool.co/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>            
43
+            <button class="btn btn-icon btn-info-outline" onclick="window.open('https://bitcoinfaucet.uo1.net/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>            
44
+            <button class="btn btn-icon btn-info-outline" onclick="window.open('https://coinfaucet.eu/en/btc-testnet/', '_blank')"><clr-icon shape="fuel"></clr-icon></button>            
45
+            <button class="btn btn-icon btn-success-outline" onclick="window.open('https://www.google.com/search?q=btc+testnet+faucet&oq=btc+testnet+faucet', '_blank')"><clr-icon shape="plus"></clr-icon> more</button>            
46
+        </div>
47
+        <div class="card-footer" >
48
+            Get free test-BTC
49
+        </div>
50
+        
51
+        
52
+    </div>
53
+</div>
54
+
55
+<!-- row 1 -->
56
+<div class="clr-all-12">
57
+
58
+    <!-- wallet picker + send box + balance box -->
59
+    <div class="clr-row">
60
+        
61
+        <!-- left column -->
62
+        <div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-5 clr-col-xl-5">
63
+
64
+            <!-- Wallet picker -->
65
+            <div class="clr-row">
66
+                <walletpicker class="clr-all-12"></walletpicker>
67
+            </div>
68
+            <!-- /Wallet picker -->
69
+
70
+            <!-- Send box -->
71
+            <div *ngIf="mode.type=='private'" class="clr-row">
72
+
73
+                <div class="clr-all-12">
74
+                    <div class="card">
75
+                        
76
+                        <div class="card-header">
77
+                            Send
78
+                        </div>
79
+                        <clr-alert *ngFor="let error of errors"  [clrAlertType]="'danger'">
80
+                            <clr-alert-item>
81
+                                <span class="alert-text">
82
+                                    {{error}}
83
+                                </span>
84
+                            </clr-alert-item>
85
+                        </clr-alert>
86
+                        <clr-alert *ngFor="let success of successes"  [clrAlertType]="'success'">
87
+                            <clr-alert-item>
88
+                                <span class="alert-text">
89
+                                    {{success}}
90
+                                </span>
91
+                            </clr-alert-item>
92
+                        </clr-alert>
93
+                        <div class="card-block" >
94
+                            
95
+                            <div class="clr-row" style="padding-left:12px; padding-right:12px">
96
+                                <input [(ngModel)]="sendTo" type="text" placeholder="To address" class="clr-input clr-col-12" />
97
+                                <input [(ngModel)]="sendAmount" type="text" min="0" placeholder="Amount" class="clr-input clr-col-12" style="margin-top:12px; margin-borrom:12px" />
98
+                            </div>
99
+                        </div>
100
+                        <div class="card-footer" >
101
+                            <button class="btn btn-success-outline" (click)="createAndSendTx(sendTo, sendAmount)">send</button>
102
+                        </div>
103
+
104
+                    </div>
105
+                </div>
106
+
107
+            </div>
108
+            <!-- /Send Box -->
109
+
110
+        </div>
111
+        <!-- /left column -->
112
+        
113
+        <!-- right column -->
114
+        <div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-7 clr-col-xl-7" >
115
+            
116
+            <!-- Balance box -->
117
+            <div *ngIf="rootkey.publicExtendedKey != ''" class="card">
118
+                <div class="card-header" style="word-break: break-all">
119
+                    <span *ngIf="mode.net=='testnet'" class="label label-danger">Testnet</span>{{rootkey.publicExtendedKey}} 
120
+                </div>
121
+                <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
122
+                    <clr-alert-item>
123
+                        <span class="alert-text">
124
+                            {{line.message}}
125
+                        </span>
126
+                    </clr-alert-item>
127
+                </clr-alert>
128
+            
129
+                <div class="card-block">
130
+                    <h3 style="margin-top:12px">{{balanceDisplay.total | number: '1.8-18'}} BTC </h3>
131
+                    <p class="monospace">
132
+                        <span *ngIf="balanceDisplay.unconfirmed < 0">&nbsp;</span>{{balanceDisplay.confirmed | number: '1.18'}} BTC confirmed<br />
133
+                        {{balanceDisplay.unconfirmed | number: '1.18'}} BTC pending
134
+                    </p>
135
+                    <p *ngIf="mode.type=='private'">
136
+                        <clr-icon shape="key"></clr-icon> Private key available
137
+                    </p>
138
+                </div>
139
+
140
+                <div class="card-footer" >
141
+                    <button class="btn btn-icon" (click)="reload()"> <clr-icon shape="refresh"></clr-icon> Reload </button>
142
+                </div>
143
+            </div>
144
+            <!-- Balance box-->
145
+        
146
+        </div>
147
+        <!-- right column -->
148
+
149
+    </div>
150
+    <!-- /row wallet picker + send box + balance box -->
151
+
152
+        <div class="clr-all-12" style="padding-left:0px; padding-right: 0px">
153
+            <address-viewer></address-viewer>
154
+        </div>
155
+</div>
156
+<!-- /row 1 -->
157
+  `
158
+})
159
+export class PluginsComponent implements AfterViewInit {
160
+    errors:string[] = []
161
+    successes:string[] = []
162
+    wallets:WalletStore = {}
163
+    txToSend:any
164
+    rootkey:bip32Keypair = {publicExtendedKey: "",privateExtendedKey: ""}
165
+    keys:KeyStore = {}
166
+    regaddresses:AddressLine[] = []
167
+    changeaddresses:AddressLine[] = []
168
+    balanceDisplay = {
169
+        confirmed: 0,
170
+        total: 0,
171
+        unconfirmed: 0
172
+    }
173
+    mode : {
174
+        type:keyType,
175
+        net:networkType
176
+    } = {
177
+        net: "testnet",
178
+        type: "public"
179
+    }
180
+    sendTo:string
181
+    sendAmount:number
182
+    loading = false
183
+    loadingN = 1
184
+
185
+    @ViewChild(AddressViewerComponent, {static: false})
186
+    AddressViewerComponent:AddressViewerComponent
187
+  
188
+    @ViewChild(WalletGeneratorComponent, {static: false})
189
+    WalletGeneratorComponent:WalletGeneratorComponent
190
+
191
+    @ViewChild(WalletPickerComponent, {static: false})
192
+    WalletPickerComponent:WalletPickerComponent
193
+
194
+    ngAfterViewInit(): void {
195
+        this.WalletGeneratorComponent.setParent(this)
196
+        this.WalletPickerComponent.setParent(this)
197
+        this.AddressViewerComponent.setParent(this)
198
+
199
+        window['btc'] = this
200
+
201
+        this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak")
202
+    }
203
+
204
+    addWallet(wallet){
205
+        /* store somewhere */
206
+
207
+        this.wallets[wallet.name] = wallet
208
+        this.WalletPickerComponent.setWallets(this.wallets)
209
+        
210
+    }
211
+
212
+    showWizard(){
213
+        this.WalletGeneratorComponent.open()
214
+    }
215
+
216
+    loadKey(key:string){
217
+        this.wallets = {}
218
+        this.txToSend = null
219
+        this.sendTo = ""
220
+        this.regaddresses = []
221
+
222
+        const t = hdkey.checkHDKey(key)
223
+        if(t){
224
+            console.log(t)
225
+            this.mode = t
226
+        }
227
+
228
+        this.rootkey = hdkey.HDKeyFromExtendedKey(key)
229
+        this.reload()
230
+    }
231
+
232
+    async reload():Promise<void>{
233
+        this.loading = true
234
+        this.loadingN = 1
235
+        this.keys = {}
236
+        this.balanceDisplay.total = 0
237
+        this.balanceDisplay.unconfirmed = 0
238
+        this.balanceDisplay.confirmed = 0
239
+        await this.refresh()
240
+        this.loading = false
241
+    }
242
+
243
+    async refresh():Promise<void>{
244
+        let p1 = this.updateChange()
245
+        let p2 = this.updateBalance()
246
+
247
+        let balances:GrandTotal[] = await Promise.all([p1,p2])
248
+        balances.forEach(balance => {
249
+            this.balanceDisplay.confirmed += balance.confirmed
250
+            this.balanceDisplay.total += balance.total
251
+            this.balanceDisplay.unconfirmed += balance.unconfirmed
252
+        })
253
+
254
+        this.AddressViewerComponent.setChangeAddresses(this.changeaddresses)
255
+        this.AddressViewerComponent.setRegularAddresses(this.regaddresses)
256
+    }
257
+
258
+    getAddresses(rootAddr: string, n = 10, skip = 0, changeAddresses = false): string[] | null{
259
+        let addresses: string[] = []
260
+        for(let i = skip; i < skip+n; i++){
261
+            let addr
262
+            if(changeAddresses){
263
+                addr = hdkey.deriveAddress(rootAddr, i, 'm/1/')
264
+            }else{
265
+                addr = hdkey.deriveAddress(rootAddr, i)
266
+            }
267
+            if(!addr){
268
+                return
269
+            }
270
+            addresses.push(addr)
271
+        }
272
+        return addresses
273
+    }
274
+
275
+    async fetchAddresses(addresses:string[], network:networkType): Promise<{success:boolean, address?:any, addresses?:any}>{
276
+        let lookupstr = addresses.join(',')
277
+        try{
278
+            let r = await fetch(api[network]+'/address/'+lookupstr, {
279
+                'method':'GET',
280
+                'mode':'cors',
281
+                'data': {limit: 200}
282
+            })
283
+            if(r.status !== 200){
284
+                return {success: false}
285
+            }
286
+
287
+            let json = (await r).json()
288
+            return json
289
+        }catch(e){
290
+            return {success: false}
291
+        }
292
+    }
293
+
294
+    async getAddressInfos(rootAddr: string, n = 10, skip = 0, changeAddresses = false): Promise<any>{
295
+        const t = hdkey.checkHDKey(rootAddr)
296
+        if(!t){
297
+            return false
298
+        }
299
+        const addresses = this.getAddresses(rootAddr, n, skip, changeAddresses)
300
+        return await this.fetchAddresses(addresses, t.net)
301
+        
302
+    }
303
+
304
+    async getTxs(address: string, type:networkType){
305
+        try{
306
+            let r = await fetch(api[type]+'/address/'+address, {
307
+                'method':'GET',
308
+                'mode':'cors'
309
+            })
310
+            if(r.status !== 200){
311
+                return {success: false}
312
+            }
313
+
314
+            return (await r).json()
315
+        }catch(e){
316
+            console.warn ("getTxs", e)
317
+        }
318
+    }
319
+
320
+    async getUnspentTxs(addresses:string[], type:networkType):Promise<any>{
321
+        let lookupstr = addresses.join(',')
322
+        
323
+        try{
324
+            let r = await fetch(api[type]+'/address/'+lookupstr+'/unspent', {
325
+                'method':'GET',
326
+                'mode':'cors'
327
+            })
328
+            if(r.status !== 200){
329
+                return {success: false}
330
+            }
331
+
332
+            return (await r).json()
333
+        }catch(e){
334
+            console.warn ("getUnspentTxs", e)
335
+            return {success: false}
336
+        }
337
+    }
338
+
339
+
340
+    getKeyForAddress(keyName){
341
+        return this.keys[keyName]
342
+    }
343
+
344
+    calculateTotal(addresses:AddressLine[]):GrandTotal{
345
+        const resMap: any = addresses.reduce((o1, o2)=>{o1[o2.address] = o2; return o1}, {})
346
+        const total: any = Object.values(resMap).reduce((accum:GrandTotal, obj:AddressLine) => {
347
+            accum.confirmed += parseFloat(<any>obj.confirmed.balance)
348
+            accum.unconfirmed += parseFloat(<any>obj.unconfirmed.balance)
349
+            accum.total += parseFloat(<any>obj.confirmed.balance) + parseFloat(<any>obj.unconfirmed.balance)
350
+            return accum
351
+        },{
352
+            confirmed: 0,
353
+            unconfirmed: 0,
354
+            total: 0
355
+        })
356
+        
357
+        return total
358
+    }
359
+
360
+    async updateBalance():Promise<GrandTotal>{
361
+        let n = 1
362
+        let res: { success: boolean; addresses: any; address: any; }
363
+        this.regaddresses = []
364
+        while(true){
365
+            this.loadingN = n
366
+            try{
367
+                res = await this.getAddressInfos(this.rootkey.publicExtendedKey, 115*n, 115*(n-1))
368
+            }catch(e){
369
+                break
370
+            }
371
+            if(res.success === true){
372
+                if('addresses' in res){ // multiple
373
+                    this.regaddresses = [...this.regaddresses, ...res.addresses]
374
+                }
375
+                if('address' in res){
376
+                    this.regaddresses.push(res.address)
377
+                }
378
+                n++
379
+            }else{
380
+                console.warn(`This error is a result of a failed fetch request. 
381
+These are always shown by the browser and cannot be hidden (a design decision)
382
+They are completely safe and mean no more addresses were found`)
383
+                console.log('No more balance found after attempting ', 115*n, 'addresses')
384
+                if(this.mode.type == "private"){
385
+                    for(let i = 0; i < 115*n+1; i++){
386
+                        const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i)
387
+                        const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net)
388
+                        keyN.derivation = i
389
+                        this.keys[addrN] = keyN
390
+                    }
391
+                }
392
+                break
393
+            }
394
+        }
395
+
396
+        //add an extra empty address at the end
397
+        const extraAddr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.regaddresses.length)
398
+        this.regaddresses.push({
399
+            address: <string>extraAddr,
400
+            confirmed: {balance: 0},
401
+            unconfirmed: {balance: 0},
402
+            total: {balance: 0},
403
+        })
404
+        
405
+        return this.calculateTotal(this.regaddresses)        
406
+    }
407
+
408
+    async updateChange():Promise<GrandTotal>{
409
+        let n = 1
410
+        this.changeaddresses = []
411
+        while(true){
412
+            let res
413
+            try{
414
+                res = await this.getAddressInfos(this.rootkey.publicExtendedKey, 115*n, 115*(n-1), true)
415
+            }catch(e){
416
+                break
417
+            }
418
+            if(res.success === true){
419
+                if('addresses' in res){ // multiple
420
+                    this.changeaddresses = [...this.changeaddresses, ...res.addresses]
421
+                }
422
+                if('address' in res){
423
+                    this.changeaddresses.push(res.address)
424
+                }
425
+                n++
426
+            }else{
427
+                console.warn(`This error is a result of a failed fetch request. 
428
+These are always shown by the browser and cannot be hidden (a design decision)
429
+They are completely safe and mean no more addresses were found`)
430
+                console.log('No more change balance found after attempting ', 115*n, 'addresses')
431
+                if(this.mode.type == "private"){
432
+                    //make keypairs for addresses
433
+                    for(let i = 0; i < 115*n; i++){
434
+                        const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i, 'm/1/')
435
+                        const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net)
436
+                        keyN.derivation = i
437
+                        this.keys[addrN] = keyN
438
+                    }
439
+                }
440
+                break
441
+            }
442
+        }
443
+
444
+        return this.calculateTotal(this.changeaddresses)        
445
+    }
446
+
447
+    async createAndSendTx(target:string, value:number){
448
+
449
+        if(target == null || typeof target !== 'string' ||  target === '' || value == null || value <= 0 ){
450
+            this.errors.push('Bad inputs. Expected bitcoin address and number')
451
+            return
452
+        }
453
+
454
+        if(value > this.balanceDisplay.confirmed){
455
+            this.errors.push('Insufficient funds')
456
+            return
457
+        }
458
+
459
+        let addresses: string[] = [...this.regaddresses.map(a => a.address), ...this.changeaddresses.map(a => a.address)]
460
+        const txs = await this.getUnspentTxs(addresses, this.mode.net)
461
+
462
+        if(txs == null || txs.success !== true || txs.unspent.length === 0){
463
+            this.errors.push('No UTXOs found')
464
+            return
465
+        }
466
+
467
+        const utxos = txs.unspent.map(t => {
468
+            return {
469
+                address: t.addresses[0],
470
+                txId: t.txid,
471
+                vout: t.n,
472
+                value: t.value * 100000000
473
+            }
474
+        })
475
+
476
+        let { inputs, outputs, fee } = coinselect(utxos, [{address: target, value: value*100000000}], feeRate)
477
+
478
+        // .inputs and .outputs will be undefined if no solution was found
479
+        if (!inputs || !outputs){
480
+            this.errors.push('Could not build a valid transaction')
481
+            return
482
+        }
483
+
484
+        const txb = new TransactionBuilder(networks[this.mode.net])
485
+
486
+
487
+        txb.setVersion(1)
488
+        inputs.forEach((input) => {
489
+            txb.addInput(input.txId, input.vout)
490
+        })
491
+        outputs.forEach((output) => {
492
+            if (!output.address) {
493
+                output.address =  hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.changeaddresses.length, 'm/1/')
494
+            }
495
+            txb.addOutput(output.address, output.value)
496
+        })
497
+        inputs.forEach((input, i) => {
498
+            txb.sign(i, <Signer>this.keys[input.address])            
499
+        })
500
+
501
+        this.txToSend = txb.build()
502
+
503
+        this.sendTx()
504
+    }
505
+
506
+    private async sendTx(){
507
+        if(this.txToSend == null){
508
+            this.errors.push('No transaction to send!')
509
+            return
510
+        }
511
+
512
+        let url = api[this.mode.net]+"/pushtx"
513
+        let r
514
+        try{
515
+            r = await fetch(url, {
516
+                'method':'POST',
517
+                'body': JSON.stringify({'hex': this.txToSend.toHex()})
518
+            })
519
+        }catch(e){
520
+           console.warn("sendTx", e) 
521
+           return
522
+        }
523
+        this.txToSend = null
524
+        if(r.status === 200){
525
+            this.errors = []
526
+            this.successes.push("Sent "+this.sendAmount+" to "+this.sendTo)
527
+            this.reload()
528
+        }else{
529
+            this.errors.push('The API '+url+' returned a non-200 error code '+r.status+' '+r.statusText)
530
+        }
531
+    }
532
+
533
+    addrExplorer(address){
534
+        const url = this.mode.net === 'testnet'?'https://live.blockcypher.com/btc-testnet/address/':'https://live.blockcypher.com/btc/address/'
535
+        window.open(url+address, '_blank')
536
+    }
537
+}
538
+
539
+export type KeyStore = {[keyname in string]: bip32Keypair & Signer}
540
+
541
+export type AddressLine = {
542
+    address: string,
543
+    confirmed: {balance: number},
544
+    unconfirmed: {balance: number},
545
+    total: {balance: number},
546
+}
547
+
548
+type networkType = 'testnet' | 'mainnet'
549
+type keyType = 'public' | 'private'
550
+type WalletStore = {
551
+    [address in string]:{
552
+        key:string
553
+        name:string
554
+    } 
555
+}
556
+type GrandTotal = {
557
+    confirmed: number,
558
+    unconfirmed: number,
559
+    total: number
560
+}
561
+type bip32Keypair = { publicExtendedKey: string, privateExtendedKey: string }
562
+
563
+const api = {
564
+    testnet: 'https://testnet-api.smartbit.com.au/v1/blockchain',
565
+    mainnet: 'https://api.smartbit.com.au/v1/blockchain'
566
+}

+ 266
- 0
src/app/Wallet/btc/wallet-generator.component.ts View File

@@ -0,0 +1,266 @@
1
+import { Component, OnInit, isDevMode, ViewChild } from '@angular/core';
2
+import { ClrWizard } from '@clr/angular';
3
+import { HDKey } from "btc-hdkey"
4
+import { PluginsComponent } from './btc.component';
5
+
6
+declare const fb
7
+@Component({
8
+    selector: 'wallet-generator', //!!!!
9
+    template: `
10
+    <clr-wizard #wizard [(clrWizardOpen)]="mdOpen" [clrWizardForceForwardNavigation]="true" clrWizardSize="lg">
11
+        <clr-wizard-title>Bitcoin wallet generator and importer</clr-wizard-title>
12
+
13
+        <clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
14
+        <clr-wizard-button [type]="'previous'">Back</clr-wizard-button>
15
+        <clr-wizard-button [type]="'next'">Next</clr-wizard-button>
16
+        <clr-wizard-button [type]="'finish'">Finish</clr-wizard-button>
17
+
18
+        <clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
19
+            <ng-template clrPageTitle>Menemonic/BIP39 (optional)</ng-template>
20
+            <ng-template clrPageNavTitle>
21
+                Menemonic/BIP39
22
+            </ng-template>
23
+            <p>
24
+            A menemonic is a collection of words that is supposed to be easier to remember than a complex seed. It may be used to reconstruct the private key.
25
+            </p>
26
+            <p>
27
+            Enter your menemonic below or generate a new one. 
28
+            </p>
29
+            <textarea [(ngModel)]="menemonic" class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px">{{menemonic}}</textarea>
30
+            <div class="clr-row">
31
+                <clr-checkbox-wrapper class="clr-col">
32
+                    <input type="checkbox" clrCheckbox value="testnet" name="testnet1" [(ngModel)]="testnet" />
33
+                    <label>testnet</label>
34
+                </clr-checkbox-wrapper>
35
+                <button class="btn btn-outline-success clr-col" (click)="generateMenemonic()">{{"Generate"}}</button>
36
+            </div>
37
+            <ng-template clrPageButtons>
38
+                <clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
39
+                <clr-wizard-button [type]="'menemonic-next'">{{menemonic.length==0?"Skip":"Next"}}</clr-wizard-button>
40
+            </ng-template>
41
+        </clr-wizard-page>
42
+
43
+        <clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
44
+            <ng-template clrPageTitle>Seed phrase (optional)</ng-template>
45
+            <ng-template clrPageNavTitle>
46
+                Seed phrase
47
+            </ng-template>
48
+            <p>
49
+            If you entered a menemonic in the previous step, a seed was generated from it.
50
+            </p>
51
+            <p>
52
+            If you would like to use your own seed enter it below. Use something sufficiently hard to guess.
53
+            </p>
54
+            <textarea class="clr-textarea" (ngModelChange)="updateSeed()" [(ngModel)]="seed" style="margin-top: 24px; width: 100%; height: 200px">{{seed}}</textarea>
55
+            <clr-checkbox-wrapper>
56
+                <input type="checkbox" clrCheckbox value="testnet" name="testnet2" [(ngModel)]="testnet" />
57
+                <label>testnet</label>
58
+            </clr-checkbox-wrapper>
59
+            <ng-template clrPageButtons>
60
+                <clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
61
+                <clr-wizard-button [type]="'back'">Back</clr-wizard-button>
62
+                <clr-wizard-button [type]="'seed-next'">{{seed.length==0?"Skip":"Next"}}</clr-wizard-button>
63
+            </ng-template>
64
+        </clr-wizard-page>
65
+
66
+        <clr-wizard-page [clrWizardPageNextDisabled]="keyInvalid" (clrWizardPageCustomButton)="doCustomClick($event)">
67
+            <ng-template clrPageTitle>Extended Key/BIP32</ng-template>
68
+            <p>
69
+            Accepts keys in the xpub and xprv (BIP32) format
70
+            </p>
71
+            <textarea [(ngModel)]="key" (ngModelChange)="updateKey()"class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px"></textarea>
72
+        </clr-wizard-page>
73
+
74
+        <clr-wizard-page class="break-word" (clrWizardPageCustomButton)="doCustomClick($event)">
75
+            <ng-template clrPageTitle>Confirm</ng-template>
76
+
77
+            <div class="alert alert-danger" role="alert">
78
+                <div class="alert-items">
79
+                    <div class="alert-item static">
80
+                        <div class="alert-icon-wrapper">
81
+                            <clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
82
+                        </div>
83
+                        <span class="alert-text">
84
+                            Do not give any of this information to other people! Even your public key holds the power to correlate all your addresses back to you! 
85
+                        </span>
86
+                    </div>
87
+                </div>
88
+            </div>
89
+            <div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-warning" role="alert">
90
+                <div class="alert-items">
91
+                    <div class="alert-item static">
92
+                        <div class="alert-icon-wrapper">
93
+                            <clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
94
+                        </div>
95
+                        <span class="alert-text">
96
+                            WARNING: A private key is not required for operating frontblock
97
+                        </span>
98
+                    </div>
99
+                </div>
100
+            </div>
101
+            <div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-info" role="alert">
102
+                <div class="alert-items">
103
+                    <div class="alert-item static">
104
+                        <div class="alert-icon-wrapper">
105
+                            <clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
106
+                        </div>
107
+                        <span class="alert-text">
108
+                            HINT: You can exclusively store the public key by going back one step and importing the xpub you just created
109
+                        </span>
110
+                    </div>
111
+                </div>
112
+            </div>
113
+            <table class="table">
114
+                <thead>
115
+                    <tr>
116
+                        <th class="left">Type</th>
117
+                        <th class="left">Value</th>
118
+                    </tr>
119
+                </thead>
120
+                <tbody>
121
+                    <tr *ngIf="menemonic.length != 0">
122
+                        <td class="left"> menemonic</td>
123
+                        <td class="left">{{menemonic}}</td>
124
+                    </tr>
125
+                    <tr *ngIf="seed.length != 0">
126
+                        <td class="left"> seed</td>
127
+                        <textarea rows="3" class="clr-textarea left" [ngModel]="seed" style="width: 100%"></textarea>
128
+                    </tr>
129
+                    <tr>
130
+                        <td class="left"> xpub</td>
131
+                        <textarea rows="3" class="clr-textarea left" [ngModel]="keypair.publicExtendedKey" style="width: 100%"></textarea>
132
+                    </tr>
133
+                    <tr *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0">
134
+                        <td class="left"> xprv</td>
135
+                        <textarea rows="3" class="clr-textarea left" [ngModel]="keypair.privateExtendedKey" style="width: 100%"></textarea>
136
+                    </tr>
137
+                </tbody>
138
+            </table>
139
+            <clr-checkbox-wrapper>
140
+                <input type="checkbox" clrCheckbox value="option1" name="options" />
141
+                <label>I understand this is important</label>
142
+            </clr-checkbox-wrapper>
143
+            <ng-template clrPageButtons>
144
+                <clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
145
+                <clr-wizard-button [type]="'back'">Back</clr-wizard-button>
146
+                <clr-wizard-button class="btn btn-success" [type]="'myfinish'">Finish</clr-wizard-button>
147
+            </ng-template>
148
+        </clr-wizard-page>
149
+    </clr-wizard>
150
+ 
151
+  `
152
+})
153
+export class WalletGeneratorComponent implements OnInit {
154
+    @ViewChild("wizard", {static: false}) wizard: ClrWizard;
155
+    key:string = ""
156
+    keyInvalid = true
157
+    accept = false
158
+    seed:string = ""
159
+    mdOpen: boolean = false
160
+    checkKey: boolean = true
161
+    menemonic:string = ""
162
+    keypair: any = {}
163
+    testnet: boolean = false
164
+    profileName
165
+    private hdKey = new HDKey()
166
+    private parent:PluginsComponent
167
+
168
+    ngOnInit() { }
169
+
170
+    open(){
171
+        this.key = ""
172
+        this.keyInvalid = true
173
+        this.seed = ""
174
+        this.checkKey = true
175
+        this.menemonic = ""
176
+        this.keypair = {}
177
+        this.testnet = false
178
+        this.wizard.reset()
179
+        this.mdOpen = true
180
+    }
181
+
182
+    setParent(parent:PluginsComponent){
183
+        this.parent = parent
184
+    }
185
+
186
+    async generateMenemonic(){
187
+        this.menemonic = this.hdKey.randomMenemonic()
188
+    }
189
+
190
+    async seedFromMenemonic(){
191
+        try{
192
+            this.seed = this.hdKey.seedFromMenemonic(this.menemonic)
193
+        }catch(e){
194
+            /* invalid menemonic */
195
+            console.log(e)
196
+        }
197
+    }
198
+
199
+    async accountFromSeed(){
200
+        this.keypair = this.hdKey.deriveAccount(this.seed, this.testnet?"testnet":"mainnet", 0)
201
+        console.log(this.keypair)
202
+        this.key = this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey
203
+        this.keyInvalid = false
204
+    }
205
+
206
+    async importKey(){
207
+        this.hdKey.HDKeyFromExtendedKey(this.key)
208
+    }
209
+
210
+    checkBip32(){
211
+        return this.hdKey.checkHDKey(this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey)
212
+    }
213
+
214
+    finish(){
215
+        this.parent.addWallet({
216
+            name: this.key.substr(4,15),
217
+            key: this.key
218
+        })
219
+    }
220
+
221
+    public doCustomClick(buttonType: string): void {
222
+        if ("menemonic-next" === buttonType) {
223
+            if(this.menemonic.length != 0){
224
+                this.seedFromMenemonic()
225
+            }
226
+            this.wizard.next();
227
+        }
228
+
229
+        if ("myfinish" === buttonType) {
230
+            this.finish()
231
+            this.wizard.close()
232
+        }
233
+
234
+        if ("seed-next" === buttonType) {
235
+            if(this.seed.length != 0){
236
+                this.accountFromSeed()
237
+            }
238
+            this.wizard.next();
239
+        }
240
+
241
+        if ("back" === buttonType) {
242
+            this.wizard.previous();
243
+        }
244
+    }
245
+
246
+    updateSeed(){
247
+        this.menemonic = ""
248
+    }
249
+
250
+    updateKey(){
251
+        this.keyInvalid = true
252
+        this.menemonic = ""
253
+        this.seed = ""
254
+        let key
255
+        try{
256
+            key = this.hdKey.HDKeyFromExtendedKey(this.key)
257
+        }catch(e){
258
+            /* invalid key */
259
+            console.log("bad key")
260
+        }
261
+        if(key){
262
+            this.keypair = key
263
+            this.keyInvalid = false
264
+        }
265
+    }
266
+}

+ 50
- 0
src/app/Wallet/btc/wallet-picker.component.ts View File

@@ -0,0 +1,50 @@
1
+
2
+
3
+import { Component, OnInit, isDevMode } from '@angular/core';
4
+import { PluginsComponent } from './btc.component';
5
+declare const fb
6
+@Component({
7
+    selector: 'walletpicker', //!!!!
8
+
9
+    template: `
10
+    <div class="card" style="overflow:hidden; padding-bottom:12px">
11
+        <div class="card-header">
12
+            Wallets
13
+        </div>
14
+        <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
15
+            <clr-alert-item>
16
+                <span class="alert-text">
17
+                    {{line.message}}
18
+                </span>
19
+            </clr-alert-item>
20
+        </clr-alert>
21
+
22
+        <clr-tree-node style="margin-top:12px; margin-bottom:12px">
23
+            <a style="cursor:pointer" (click)="parent.showWizard()"><clr-icon shape="download" class="is-solid"></clr-icon> New / Import</a>
24
+        </clr-tree-node>
25
+        <clr-tree-node *ngFor="let line of wallets">
26
+            <a style="cursor:pointer" (click)="parent.loadKey(line.key)">
27
+                <clr-icon *ngIf="line.key.startsWith('xpub') || line.key.startsWith('tpub')" shape="lock" class="is-solid"></clr-icon>
28
+                <clr-icon *ngIf="line.key.startsWith('xprv') || line.key.startsWith('tprv')" shape="key" class="is-solid"></clr-icon>
29
+                {{line.name}}
30
+            </a>
31
+        </clr-tree-node>        
32
+    </div> 
33
+  `
34
+})
35
+export class WalletPickerComponent implements OnInit {
36
+    private parent:PluginsComponent
37
+    wallets = []
38
+
39
+    setWallets(wallets){
40
+        this.wallets = Object.values(wallets)
41
+    }
42
+
43
+    setParent(parent:PluginsComponent){
44
+        this.parent = parent
45
+    }
46
+
47
+    ngOnInit() { }
48
+
49
+}
50
+

+ 60
- 0
src/app/Wallet/module.ts View File

@@ -0,0 +1,60 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { RouterModule } from '@angular/router';
4
+
5
+import { PluginsComponent } from './btc/btc.component';
6
+import { WalletPickerComponent } from './btc/wallet-picker.component';
7
+import { AddressViewerComponent } from './btc/address-viewer.component';
8
+
9
+import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
10
+import { ClarityModule } from '@clr/angular';
11
+import { FormsModule } from '@angular/forms';
12
+import { WalletGeneratorComponent } from './btc/wallet-generator.component';
13
+
14
+
15
+@NgModule({
16
+  imports: [
17
+    FormsModule,
18
+    ClarityModule,
19
+
20
+    CommonModule, 
21
+    RouterModule.forChild([
22
+      {path: "btc", component: PluginsComponent},
23
+    ]),
24
+  ],
25
+  exports: [RouterModule],
26
+  declarations: [
27
+    PluginsComponent,
28
+    WalletPickerComponent,
29
+    AddressViewerComponent,
30
+    WalletGeneratorComponent
31
+  ]
32
+})
33
+export class PluginModule implements FrontendPlugin{
34
+  getSidebarEntry(): SidebarEntries {
35
+    return {
36
+      icon: "wallet",
37
+      text: "Wallets",
38
+      parentRoute: "wallet",
39
+      links: [{
40
+        route: "btc",
41
+        text: "BTC Wallet"    
42
+      },{
43
+        route: "ltc",
44
+        text: "LTC Wallet"    
45
+      },{
46
+        route: "eth",
47
+        text: "ETH Wallet"    
48
+      },{
49
+        route: "xrp",
50
+        text: "XRP Wallet"    
51
+      },{
52
+        route: "xlm",
53
+        text: "XLM Wallet"    
54
+      },{
55
+        route: "settings",
56
+        text: "Settings"    
57
+      }]
58
+    }
59
+  }
60
+}

+ 24
- 0
src/app/app-routing.module.ts View File

@@ -0,0 +1,24 @@
1
+import { NgModule } from '@angular/core';
2
+import { Routes, RouterModule, Route } from '@angular/router';
3
+import { HomeComponent } from './home/home.component';
4
+import { ErrorDisplayComponent } from './error-display/error-display.component';
5
+import { environment } from 'src/environments/environment';
6
+
7
+const routes: Routes = [{
8
+  path: "",
9
+  component: HomeComponent
10
+},{
11
+  path: "**",
12
+  component: ErrorDisplayComponent
13
+}];
14
+
15
+export function getRoutes(){
16
+  return routes
17
+}
18
+
19
+@NgModule({
20
+  imports: [RouterModule.forRoot(routes)],
21
+  exports: [RouterModule],
22
+})
23
+export class AppRoutingModule {
24
+}

+ 5
- 0
src/app/app.component.html View File

@@ -0,0 +1,5 @@
1
+<clr-main-container  >
2
+    <header-bar></header-bar>
3
+    <subnav></subnav>
4
+    <dynamic-loader style="height:  100%;"></dynamic-loader>
5
+</clr-main-container>

+ 0
- 0
src/app/app.component.scss View File


+ 35
- 0
src/app/app.component.spec.ts View File

@@ -0,0 +1,35 @@
1
+import { TestBed, async } from '@angular/core/testing';
2
+import { RouterTestingModule } from '@angular/router/testing';
3
+import { AppComponent } from './app.component';
4
+
5
+describe('AppComponent', () => {
6
+  beforeEach(async(() => {
7
+    TestBed.configureTestingModule({
8
+      imports: [
9
+        RouterTestingModule
10
+      ],
11
+      declarations: [
12
+        AppComponent
13
+      ],
14
+    }).compileComponents();
15
+  }));
16
+
17
+  it('should create the app', () => {
18
+    const fixture = TestBed.createComponent(AppComponent);
19
+    const app = fixture.debugElement.componentInstance;
20
+    expect(app).toBeTruthy();
21
+  });
22
+
23
+  it(`should have as title 'dashboard'`, () => {
24
+    const fixture = TestBed.createComponent(AppComponent);
25
+    const app = fixture.debugElement.componentInstance;
26
+    expect(app.title).toEqual('dashboard');
27
+  });
28
+
29
+  it('should render title in a h1 tag', () => {
30
+    const fixture = TestBed.createComponent(AppComponent);
31
+    fixture.detectChanges();
32
+    const compiled = fixture.debugElement.nativeElement;
33
+    expect(compiled.querySelector('h1').textContent).toContain('Welcome to dashboard!');
34
+  });
35
+});

+ 32
- 0
src/app/app.component.ts View File

@@ -0,0 +1,32 @@
1
+import { Component, ChangeDetectorRef, NgZone, ViewChild, OnInit, AfterContentInit, isDevMode } from '@angular/core';
2
+import { SidebarComponent } from './sidebar/sidebar.component';
3
+import { SidebarEntryService } from './sidebar-entry-service.service';
4
+@Component({
5
+  selector: 'app-root',
6
+  templateUrl: './app.component.html',
7
+  styleUrls: ['./app.component.scss']
8
+})
9
+export class AppComponent implements AfterContentInit {
10
+  title = 'dashboard';
11
+
12
+  @ViewChild(SidebarComponent, {static: false})
13
+  sidebar: SidebarComponent
14
+  
15
+  constructor(private zone:NgZone, private sidebarService: SidebarEntryService){
16
+    window["refresh"] = setInterval(() => zone.run(()=>{ /*this triggers an angular reload*/}), 200)
17
+    if(isDevMode()){
18
+      require("../assets/FrontblockLib")
19
+    }
20
+  }
21
+
22
+  ngAfterContentInit(){
23
+    const _this = this;
24
+    (function awaitSidebar(){
25
+      if(_this.sidebar != null){
26
+        _this.sidebarService.setSidebar(_this.sidebar)
27
+        return
28
+      }
29
+      setTimeout(awaitSidebar,25)
30
+    })()
31
+  }
32
+}

+ 69
- 0
src/app/app.module.ts View File

@@ -0,0 +1,69 @@
1
+import { NgModule } from '@angular/core';
2
+import { AppRoutingModule } from './app-routing.module';
3
+
4
+import { ClarityModule } from '@clr/angular';
5
+import { BrowserAnimationsModule } from "@angular/platform-browser/animations";
6
+import { BrowserModule } from '@angular/platform-browser';
7
+import { FormsModule } from '@angular/forms';
8
+
9
+import { COMPILER_OPTIONS, CompilerFactory, Compiler } from '@angular/core';
10
+import { JitCompilerFactory } from '@angular/platform-browser-dynamic';
11
+
12
+import { AppComponent } from './app.component';
13
+import { SidebarComponent } from './sidebar/sidebar.component';
14
+import { MainComponent } from './content-area/main.component';
15
+import { DynamicLoaderComponent } from './dynamic-loader/dynamic-loader.component';
16
+import { HomeComponent } from './home/home.component';
17
+import { ErrorDisplayComponent } from './error-display/error-display.component';
18
+import { HeaderBarComponent } from './header-bar/header-bar.component';
19
+import { SubnavComponent } from './subnav/subnav.component';
20
+
21
+import { environment } from 'src/environments/environment';
22
+
23
+export function createCompiler(fn: CompilerFactory): Compiler {
24
+  return fn.createCompiler();
25
+}
26
+
27
+const declarations = [ 
28
+  AppComponent,
29
+  
30
+  SidebarComponent,
31
+  MainComponent,
32
+  DynamicLoaderComponent,
33
+  HomeComponent,
34
+  HeaderBarComponent,
35
+  ErrorDisplayComponent,
36
+  SubnavComponent,
37
+]
38
+
39
+@NgModule({
40
+  declarations: declarations,
41
+  imports: [
42
+    FormsModule,
43
+    BrowserModule,
44
+    BrowserAnimationsModule,
45
+    ClarityModule,
46
+    AppRoutingModule,
47
+  ],
48
+  entryComponents: [],
49
+  providers: [
50
+    {
51
+      provide: COMPILER_OPTIONS,
52
+      useValue: {},
53
+      multi: true
54
+    },
55
+    {
56
+      provide: CompilerFactory,
57
+      useClass: JitCompilerFactory,
58
+      deps: [COMPILER_OPTIONS]
59
+    },
60
+    {
61
+      provide: Compiler,
62
+      useFactory: createCompiler,
63
+      deps: [CompilerFactory]
64
+    }
65
+  ],
66
+  bootstrap: [AppComponent]
67
+})
68
+export class AppModule{
69
+}

+ 3
- 0
src/app/content-area/main.component.html View File

@@ -0,0 +1,3 @@
1
+<div  class="content-area" style="max-height: 100%">
2
+    <router-outlet class="clr-all-12" ></router-outlet>
3
+</div>

+ 0
- 0
src/app/content-area/main.component.scss View File


+ 25
- 0
src/app/content-area/main.component.spec.ts View File

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { MainComponent } from './main.component';
4
+
5
+describe('MainComponent', () => {
6
+  let component: MainComponent;
7
+  let fixture: ComponentFixture<MainComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ MainComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(MainComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 17
- 0
src/app/content-area/main.component.ts View File

@@ -0,0 +1,17 @@
1
+import { Component, OnInit, ViewContainerRef, ViewChild } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'content-area',
5
+  templateUrl: './main.component.html',
6
+  styleUrls: ['./main.component.scss']
7
+})
8
+export class MainComponent implements OnInit {
9
+
10
+  @ViewChild('content', { read: ViewContainerRef, static: false }) 
11
+  content: ViewContainerRef;
12
+
13
+  constructor() { }
14
+
15
+  ngOnInit() {
16
+  }
17
+}

+ 4
- 0
src/app/dynamic-loader/dynamic-loader.component.html View File

@@ -0,0 +1,4 @@
1
+<div class="content-container" style="height:  100%;">
2
+    <sidebar></sidebar>
3
+    <content-area style="height:  100%; width: 100%"></content-area>
4
+</div>

+ 3
- 0
src/app/dynamic-loader/dynamic-loader.component.scss View File

@@ -0,0 +1,3 @@
1
+.heightmax{
2
+    height: 100%!important;
3
+}

+ 25
- 0
src/app/dynamic-loader/dynamic-loader.component.spec.ts View File

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { DynamicLoaderComponent } from './dynamic-loader.component';
4
+
5
+describe('DynamicLoaderComponent', () => {
6
+  let component: DynamicLoaderComponent;
7
+  let fixture: ComponentFixture<DynamicLoaderComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ DynamicLoaderComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(DynamicLoaderComponent);
18
+    component = fixture.componentInstance;
19
+    fixture.detectChanges();
20
+  });
21
+
22
+  it('should create', () => {
23
+    expect(component).toBeTruthy();
24
+  });
25
+});

+ 115
- 0
src/app/dynamic-loader/dynamic-loader.component.ts View File

@@ -0,0 +1,115 @@
1
+/**
2
+ * Set existing vendor modules into SystemJS registry.
3
+ * This way SystemJS won't make HTTP requests to fetch imported modules
4
+ * needed by the dynamicaly loaded Widgets.
5
+ */
6
+import { System } from 'systemjs';
7
+declare const SystemJS: System;
8
+
9
+import * as angularRouter from '@angular/router';
10
+import * as angularCore from '@angular/core';
11
+import * as angularCommon from '@angular/common';
12
+import * as angularCommonHttp from '@angular/common/http';
13
+import * as angularForms from '@angular/forms';
14
+import * as angularAnimations from '@angular/animations';
15
+import * as angularAnimationsBrowser from '@angular/animations/browser'
16
+import * as angularPlatformBrowser from '@angular/platform-browser';
17
+import * as angularPlatformBrowserDynamic from '@angular/platform-browser-dynamic';
18
+import * as clarityModule from '@clr/angular';
19
+import * as frontblockGenericTypes from 'frontblock-generic/Types.js'
20
+import * as browserAnimationsModule from "@angular/platform-browser/animations";
21
+
22
+SystemJS.set('@clr/angular', SystemJS.newModule(clarityModule));
23
+SystemJS.set('@angular/router', SystemJS.newModule(angularRouter));
24
+SystemJS.set('@angular/core', SystemJS.newModule(angularCore));
25
+SystemJS.set('@angular/common', SystemJS.newModule(angularCommon));
26
+SystemJS.set('@angular/common/http', SystemJS.newModule(angularCommonHttp));
27
+SystemJS.set('@angular/forms', SystemJS.newModule(angularForms));
28
+SystemJS.set('@angular/animations', SystemJS.newModule(angularAnimations));
29
+SystemJS.set('@angular/animations/browser', SystemJS.newModule(angularAnimationsBrowser));
30
+SystemJS.set('@angular/platform-browser', SystemJS.newModule(angularPlatformBrowser));