Browse Source

fix admin not exporting own RPCs

master
peter 1 year ago
parent
commit
06e9ac91a0
100 changed files with 26952 additions and 25 deletions
  1. 4
    1
      .gitignore
  2. 3
    3
      package-lock.json
  3. 5
    5
      package.json
  4. 8
    0
      src/backend/FrontblockAdmin.ts
  5. 1
    16
      src/backend/UpdateManger.ts
  6. 60
    0
      src/frontend/.drone.yml
  7. 13
    0
      src/frontend/.editorconfig
  8. 48
    0
      src/frontend/.gitignore
  9. 6
    0
      src/frontend/.gitmodules
  10. 27
    0
      src/frontend/README.md
  11. 147
    0
      src/frontend/angular.json
  12. 12
    0
      src/frontend/browserslist
  13. 32
    0
      src/frontend/e2e/protractor.conf.js
  14. 23
    0
      src/frontend/e2e/src/app.e2e-spec.ts
  15. 11
    0
      src/frontend/e2e/src/app.po.ts
  16. 13
    0
      src/frontend/e2e/tsconfig.json
  17. 32
    0
      src/frontend/karma.conf.js
  18. 12512
    0
      src/frontend/package-lock.json
  19. 70
    0
      src/frontend/package.json
  20. 9
    0
      src/frontend/proxy.conf.json
  21. 69
    0
      src/frontend/src/app/apiclient/.drone.yml
  22. 10
    0
      src/frontend/src/app/apiclient/.gitignore
  23. 2
    0
      src/frontend/src/app/apiclient/.npmignore
  24. 17
    0
      src/frontend/src/app/apiclient/conf/ApiClient.json
  25. 4652
    0
      src/frontend/src/app/apiclient/package-lock.json
  26. 45
    0
      src/frontend/src/app/apiclient/package.json
  27. 104
    0
      src/frontend/src/app/apiclient/src/backend/FrontblockApiClient.ts
  28. 201
    0
      src/frontend/src/app/apiclient/src/backend/Plugin.ts
  29. 38
    0
      src/frontend/src/app/apiclient/src/backend/webpack.prod.js
  30. 92
    0
      src/frontend/src/app/apiclient/src/frontend/apiclient-consumptions.component.ts
  31. 134
    0
      src/frontend/src/app/apiclient/src/frontend/apiclient-settings-form.component.ts
  32. 101
    0
      src/frontend/src/app/apiclient/src/frontend/apiclient-subscriptions.component.ts
  33. 52
    0
      src/frontend/src/app/apiclient/src/frontend/module.ts
  34. 41
    0
      src/frontend/src/app/apiclient/src/frontend/webpack.prod.js
  35. 10
    0
      src/frontend/src/app/apiclient/tsconfig.frontend.json
  36. 18
    0
      src/frontend/src/app/apiclient/tsconfig.json
  37. 26
    0
      src/frontend/src/app/app-routing.module.ts
  38. 5
    0
      src/frontend/src/app/app.component.html
  39. 0
    0
      src/frontend/src/app/app.component.scss
  40. 35
    0
      src/frontend/src/app/app.component.spec.ts
  41. 32
    0
      src/frontend/src/app/app.component.ts
  42. 69
    0
      src/frontend/src/app/app.module.ts
  43. 3
    0
      src/frontend/src/app/content-area/main.component.html
  44. 0
    0
      src/frontend/src/app/content-area/main.component.scss
  45. 25
    0
      src/frontend/src/app/content-area/main.component.spec.ts
  46. 17
    0
      src/frontend/src/app/content-area/main.component.ts
  47. 4
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.html
  48. 3
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.scss
  49. 25
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.spec.ts
  50. 133
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.ts
  51. 15
    0
      src/frontend/src/app/error-display/a-nested.component.ts
  52. 1
    0
      src/frontend/src/app/error-display/error-display.component.html
  53. 0
    0
      src/frontend/src/app/error-display/error-display.component.scss
  54. 25
    0
      src/frontend/src/app/error-display/error-display.component.spec.ts
  55. 15
    0
      src/frontend/src/app/error-display/error-display.component.ts
  56. 22
    0
      src/frontend/src/app/error-display/module.ts
  57. 12
    0
      src/frontend/src/app/header-bar/header-bar.component.html
  58. 0
    0
      src/frontend/src/app/header-bar/header-bar.component.scss
  59. 25
    0
      src/frontend/src/app/header-bar/header-bar.component.spec.ts
  60. 15
    0
      src/frontend/src/app/header-bar/header-bar.component.ts
  61. 1
    0
      src/frontend/src/app/home/home.component.html
  62. 0
    0
      src/frontend/src/app/home/home.component.scss
  63. 25
    0
      src/frontend/src/app/home/home.component.spec.ts
  64. 15
    0
      src/frontend/src/app/home/home.component.ts
  65. 15
    0
      src/frontend/src/app/htmlsupplier/a-nested.component.ts
  66. 24
    0
      src/frontend/src/app/htmlsupplier/component.ts
  67. 21
    0
      src/frontend/src/app/htmlsupplier/module.ts
  68. 1
    0
      src/frontend/src/app/knex-config/knex-config.component.html
  69. 0
    0
      src/frontend/src/app/knex-config/knex-config.component.scss
  70. 25
    0
      src/frontend/src/app/knex-config/knex-config.component.spec.ts
  71. 196
    0
      src/frontend/src/app/knex-config/knex-config.component.ts
  72. 23
    0
      src/frontend/src/app/knex-config/knex-config.module.ts
  73. 15
    0
      src/frontend/src/app/paymentmanager/a-nested.component.ts
  74. 24
    0
      src/frontend/src/app/paymentmanager/component.ts
  75. 22
    0
      src/frontend/src/app/paymentmanager/module.ts
  76. 47
    0
      src/frontend/src/app/pluginmanager/debug.component.ts
  77. 31
    0
      src/frontend/src/app/pluginmanager/module.ts
  78. 156
    0
      src/frontend/src/app/pluginmanager/plugins.component.ts
  79. 20
    0
      src/frontend/src/app/sidebar-entry-service.service.ts
  80. 19
    0
      src/frontend/src/app/sidebar/sidebar.component.html
  81. 0
    0
      src/frontend/src/app/sidebar/sidebar.component.scss
  82. 25
    0
      src/frontend/src/app/sidebar/sidebar.component.spec.ts
  83. 36
    0
      src/frontend/src/app/sidebar/sidebar.component.ts
  84. 7
    0
      src/frontend/src/app/subnav/subnav.component.html
  85. 0
    0
      src/frontend/src/app/subnav/subnav.component.scss
  86. 25
    0
      src/frontend/src/app/subnav/subnav.component.spec.ts
  87. 15
    0
      src/frontend/src/app/subnav/subnav.component.ts
  88. 59
    0
      src/frontend/src/app/wallet/.drone.yml
  89. 11
    0
      src/frontend/src/app/wallet/.gitignore
  90. 5477
    0
      src/frontend/src/app/wallet/package-lock.json
  91. 57
    0
      src/frontend/src/app/wallet/package.json
  92. 118
    0
      src/frontend/src/app/wallet/src/backend/Plugin.ts
  93. 37
    0
      src/frontend/src/app/wallet/src/backend/webpack.prod.js
  94. 174
    0
      src/frontend/src/app/wallet/src/frontend/btc/address-viewer.component.ts
  95. 702
    0
      src/frontend/src/app/wallet/src/frontend/btc/btc.component.ts
  96. 266
    0
      src/frontend/src/app/wallet/src/frontend/btc/wallet-generator.component.ts
  97. 50
    0
      src/frontend/src/app/wallet/src/frontend/btc/wallet-picker.component.ts
  98. 67
    0
      src/frontend/src/app/wallet/src/frontend/module.ts
  99. 17
    0
      src/frontend/src/app/wallet/src/frontend/settings.component.ts
  100. 0
    0
      src/frontend/src/app/wallet/src/frontend/webpack.prod.js

+ 4
- 1
.gitignore View File

@@ -13,4 +13,7 @@ conf
13 13
 *.ts
14 14
 
15 15
 !src/**/*
16
-node_modules
16
+node_modules
17
+src/frontend/node_modules
18
+src/frontend/out-tsc
19
+src/frontend/dist

+ 3
- 3
package-lock.json View File

@@ -2123,9 +2123,9 @@
2123 2123
       }
2124 2124
     },
2125 2125
     "frontblock-generic": {
2126
-      "version": "0.30.1",
2127
-      "resolved": "https://registry.npmjs.org/frontblock-generic/-/frontblock-generic-0.30.1.tgz",
2128
-      "integrity": "sha512-r+4/Zq9WGkD6Ma9F2AQp2ZP546jgXLde7aB050HHwF9t0O4ewy+ohfgyvEEH3dpIoQBbUWuD9uaAtahpNUYcqA==",
2126
+      "version": "0.30.2",
2127
+      "resolved": "https://registry.npmjs.org/frontblock-generic/-/frontblock-generic-0.30.2.tgz",
2128
+      "integrity": "sha512-9dSasjv5pth2j6sCJ1cACfVWjTcDuabu/Nki8OBDXE3z4dljw1HnM5fRR7ncbnk7FAnUJXiMuWNBRqi3At3PLA==",
2129 2129
       "requires": {
2130 2130
         "@types/lowdb": "^1.0.9",
2131 2131
         "@types/node": "^12.7.1",

+ 5
- 5
package.json View File

@@ -5,12 +5,12 @@
5 5
   "scripts": {
6 6
     "tsc": "tsc",
7 7
     "start": "npm run build; node dist/FrontblockAdmin.js",
8
-    "build": "npm run clean; npm run build-backend",
8
+    "build": "npm run clean; npm run build-backend; npm run build-frontend",
9 9
     "build-backend": "tsc; npm run webpack",
10
-    "build-frontend": "npm run build-dashboard",
11
-    "build-dashboard": "git submodule init && git submodule update --merge; cd src/frontend/dashboard; npm i && npm run build; cp -r dist/* ../../../static",
10
+    "build-frontend": "npm run build-dashboard; cp ./dist/FrontblockLib.js ./static",
11
+    "build-dashboard": "git submodule init && git submodule update --merge; cd src/frontend; npm i && npm run build; mkdir ../../static; cp -r dist/* ../../static",
12 12
     "build-widget": "rollup -c src/frontend/widget/rollup.config.js; mkdir -p plugins/PluginManager; cp FrontendPlugin.js plugins/PluginManager",
13
-    "clean": "rm -rf lib static plugins conf dist widget .rpt2_cache *.js *.ts src/frontend/dashboard/dist",
13
+    "clean": "rm -rf lib static plugins conf dist widget .rpt2_cache *.js *.ts src/frontend/dist",
14 14
     "update-frontblock": "rm -rf node_modules/frontblock*; npm install",
15 15
     "webpack": "webpack  --config src/backend/webpack.prod.js --progress --colors"
16 16
   },
@@ -27,7 +27,7 @@
27 27
     "debug": "^4.1.1",
28 28
     "express": "^4.16.4",
29 29
     "frontblock": "^0.14.0",
30
-    "frontblock-generic": "^0.30.1",
30
+    "frontblock-generic": "^0.30.2",
31 31
     "git-cherrypicker": "0.0.3",
32 32
     "git-describe": "^4.0.4",
33 33
     "http": "0.0.0",

+ 8
- 0
src/backend/FrontblockAdmin.ts View File

@@ -12,6 +12,7 @@ import * as path from "path"
12 12
 import { UpdateManager } from './UpdateManger';
13 13
 import { FrontblockApi } from 'frontblock-generic/Api';
14 14
 import { Plugin } from 'frontblock-generic/Plugin';
15
+import { socketioRPC } from 'frontblock-generic/RPC';
15 16
 var exec = require('child-process-promise').exec;
16 17
 
17 18
 Logger.configure({
@@ -91,6 +92,13 @@ export class FrontblockAdmin extends AdminBase<AdminConf>{
91 92
         this.startWebserver()
92 93
     }
93 94
 
95
+    exportRPCs():socketioRPC[]{
96
+        return [
97
+            ...[/* more RPC here */],
98
+            ...super.exportRPCs()
99
+        ]
100
+    }
101
+
94 102
     private startWebserver(){
95 103
         if(this.httpServer != null || this.express != null){
96 104
             logger.warn("Webserver is already running")

+ 1
- 16
src/backend/UpdateManger.ts View File

@@ -79,22 +79,7 @@ export class UpdateManager extends Plugin{
79 79
     async updatePlatformDependencies(){
80 80
 
81 81
         logger.info("installing npm stuff")
82
-
83
-/*
84
-        const res = await exec('npm install --prefix ./plugins knex sqlite3')
85
-        logger.warn(res.stderr)
86
-        logger.info(res.stdout)
87
-*/      
88
-
89
-        /*
90
-        const extensions = [
91
-            new NPMExtension("sqlite3", "./plugins", "4.1.0"),
92
-            new NPMExtension("frontblock", "./plugins", "latest")
93
-
94
-        ]
95
-        */
96
-
97
-       const extensions:NPMExtension[] = []
82
+        const extensions:NPMExtension[] = []
98 83
 
99 84
         await Promise.all(extensions.map(e => e.install()))
100 85
         const status = await Promise.all(extensions.map(e => e.status()))

+ 60
- 0
src/frontend/.drone.yml View File

@@ -0,0 +1,60 @@
1
+kind: pipeline
2
+name: default
3
+
4
+steps:
5
+- name: restore cache
6
+  image: drillster/drone-volume-cache
7
+  settings:
8
+    restore: true
9
+    mount:
10
+      - ./node_modules
11
+  volumes:
12
+  - name: cache
13
+    path: /cache
14
+    
15
+- name: npm install
16
+  image: node:12
17
+  commands:
18
+  - npm install
19
+
20
+- name: npm run build
21
+  image: node:12
22
+  commands:
23
+  - npm run build
24
+
25
+- name: rebuild cache
26
+  image: drillster/drone-volume-cache
27
+  settings:
28
+    rebuild: true
29
+    mount:
30
+      - ./node_modules
31
+  volumes:
32
+  - name: cache
33
+    path: /cache
34
+
35
+- name: deploy static files
36
+  image: node:12
37
+  commands:
38
+  - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
39
+  - git config --global user.name "${DRONE_COMMIT_AUTHOR}"
40
+  - git clone https://gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git
41
+  - cp -r ./dist/* ./${DRONE_REPO_NAME}
42
+  - cd ./${DRONE_REPO_NAME}
43
+  - git add -A
44
+  - git commit --allow-empty -m "drone taged as version ${DRONE_TAG}"
45
+  - git tag ${DRONE_TAG}
46
+  - git push https://$GIT_USER:$GIT_PASSWORD@gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git master ${DRONE_TAG}
47
+  environment:
48
+    GIT_USER:
49
+      from_secret: git_user
50
+    GIT_PASSWORD:
51
+      from_secret: git_password
52
+  when:
53
+    event:
54
+    - tag
55
+
56
+
57
+volumes:
58
+- name: cache 
59
+  host:
60
+    path: /tmp

+ 13
- 0
src/frontend/.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

+ 48
- 0
src/frontend/.gitignore View File

@@ -0,0 +1,48 @@
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
47
+
48
+src/assets/*.js

+ 6
- 0
src/frontend/.gitmodules View File

@@ -0,0 +1,6 @@
1
+[submodule "src/app/apiclient"]
2
+	path = src/app/apiclient
3
+	url = ssh://git@gitea.frontblock.me:2222/fb-plugin/apiclient.git
4
+[submodule "src/app/wallet"]
5
+	path = src/app/wallet
6
+	url = ssh://git@gitea.frontblock.me:2222/fb-plugin/wallet.git

+ 27
- 0
src/frontend/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).

+ 147
- 0
src/frontend/angular.json View File

@@ -0,0 +1,147 @@
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",
21
+            "index": "src/index.html",
22
+            "main": "src/main.ts",
23
+            "polyfills": "src/polyfills.ts",
24
+            "tsConfig": "tsconfig.app.json",
25
+            
26
+            "aot": false,
27
+            "assets": [
28
+              "src/favicon.ico",
29
+              "src/assets"
30
+            ],
31
+            "styles": [
32
+              "src/styles.scss",
33
+              "node_modules/@clr/icons/clr-icons.min.css",
34
+              "node_modules/@clr/ui/clr-ui-dark.min.css"
35
+            ],
36
+            "scripts": [
37
+              "node_modules/systemjs/dist/system.js",
38
+              "node_modules/@webcomponents/custom-elements/custom-elements.min.js",
39
+              "node_modules/@clr/icons/clr-icons.min.js"
40
+            ]
41
+          },
42
+          "configurations": {
43
+            "production": {
44
+              "tsConfig": "tsconfig.prod.json",
45
+              "fileReplacements": [
46
+                {
47
+                  "replace": "src/environments/environment.ts",
48
+                  "with": "src/environments/environment.prod.ts"
49
+                }
50
+              ],
51
+              "optimization": true,
52
+              "outputHashing": "all",
53
+              "sourceMap": false,
54
+              "extractCss": true,
55
+              "namedChunks": false,
56
+              "aot": false,
57
+              "extractLicenses": true,
58
+              "vendorChunk": false,
59
+              "buildOptimizer": true,
60
+              "budgets": [
61
+                {
62
+                  "type": "initial",
63
+                  "maximumWarning": "2mb",
64
+                  "maximumError": "5mb"
65
+                }
66
+              ]
67
+            },
68
+
69
+            "prodlike": {
70
+              "tsConfig": "tsconfig.prod.json",
71
+              "fileReplacements": [
72
+                {
73
+                  "replace": "src/environments/environment.ts",
74
+                  "with": "src/environments/environment.prodlike.ts"
75
+                }
76
+              ]
77
+            }
78
+          }
79
+        },
80
+        "serve": {
81
+          "builder": "@angular-devkit/build-angular:dev-server",
82
+          "tsConfig": "tsconfig.app.json",
83
+          "options": {
84
+            "browserTarget": "dashboard:build"
85
+          },
86
+          "configurations": {
87
+            "production": {
88
+              "browserTarget": "dashboard:build:production"
89
+            },
90
+            "prodlike": {
91
+              "browserTarget": "dashboard:build:prodlike"
92
+            }
93
+          }
94
+        },
95
+        "extract-i18n": {
96
+          "builder": "@angular-devkit/build-angular:extract-i18n",
97
+          "options": {
98
+            "browserTarget": "dashboard:build"
99
+          }
100
+        },
101
+        "test": {
102
+          "builder": "@angular-devkit/build-angular:karma",
103
+          "options": {
104
+            "main": "src/test.ts",
105
+            "polyfills": "src/polyfills.ts",
106
+            "tsConfig": "tsconfig.spec.json",
107
+            "karmaConfig": "karma.conf.js",
108
+            "assets": [
109
+              "src/favicon.ico",
110
+              "src/assets"
111
+            ],
112
+            "styles": [
113
+              "src/styles.scss"
114
+            ],
115
+            "scripts": []
116
+          }
117
+        },
118
+        "lint": {
119
+          "builder": "@angular-devkit/build-angular:tslint",
120
+          "options": {
121
+            "tsConfig": [
122
+              "tsconfig.app.json",
123
+              "tsconfig.spec.json",
124
+              "e2e/tsconfig.json"
125
+            ],
126
+            "exclude": [
127
+              "**/node_modules/**",
128
+              "**/backend/**"
129
+            ]
130
+          }
131
+        },
132
+        "e2e": {
133
+          "builder": "@angular-devkit/build-angular:protractor",
134
+          "options": {
135
+            "protractorConfig": "e2e/protractor.conf.js",
136
+            "devServerTarget": "dashboard:serve"
137
+          },
138
+          "configurations": {
139
+            "production": {
140
+              "devServerTarget": "dashboard:serve:production"
141
+            }
142
+          }
143
+        }
144
+      }
145
+    }},
146
+  "defaultProject": "dashboard"
147
+}

+ 12
- 0
src/frontend/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
src/frontend/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
src/frontend/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
src/frontend/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
src/frontend/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
src/frontend/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
+};

+ 12512
- 0
src/frontend/package-lock.json
File diff suppressed because it is too large
View File


+ 70
- 0
src/frontend/package.json View File

@@ -0,0 +1,70 @@
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
+    "start-prodlike": "npm run build-assets && ng serve --aot=false --optimization=false --proxy-config proxy.conf.json --configuration=prodlike",
8
+    "build": "rm -f src/assets/*.js; ng build --prod --aot=false --optimization=false --build-optimizer=false",
9
+    "build-assets": "npm run copy-frontends; npm run deep-clean-plugins",
10
+    "update-frontblock": "rm -rf node_modules/frontblock*; npm install",
11
+    "get-submodules": "git submodule update --init && git submodule foreach git checkout master",
12
+    "copy-frontends": "for module in $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'); do npm i --prefix $module && npm run --prefix $module build && cp $module/dist/FrontendPlugin.js ./src/assets/$(basename $module).js; done",
13
+    "deep-clean-plugins": "for module in $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'); do rm -rf $module/node_modules; done",
14
+    "test": "ng test",
15
+    "lint": "ng lint",
16
+    "e2e": "ng e2e"
17
+  },
18
+  "private": true,
19
+  "dependencies": {
20
+    "@angular/animations": "~8.2.1",
21
+    "@angular/common": "~8.2.1",
22
+    "@angular/compiler": "~8.2.1",
23
+    "@angular/core": "~8.2.1",
24
+    "@angular/forms": "~8.2.1",
25
+    "@angular/platform-browser": "~8.2.1",
26
+    "@angular/platform-browser-dynamic": "~8.2.1",
27
+    "@angular/router": "~8.2.1",
28
+    "@clr/angular": "^2.1.1",
29
+    "@clr/icons": "^2.1.1",
30
+    "@clr/ui": "^2.1.1",
31
+    "@types/knex": "^0.16.1",
32
+    "@webcomponents/custom-elements": "^1.0.0",
33
+    "btc-hdkey": "0.0.17",
34
+    "coinselect": "^3.1.11",
35
+    "frontblock": "latest",
36
+    "frontblock-generic": "^0.23.0",
37
+    "key-file-storage": "^2.2.4",
38
+    "node-fetch": "^2.6.0",
39
+    "rxjs": "~6.5.2",
40
+    "stream": "0.0.2",
41
+    "systemjs": "^0.21.3",
42
+    "tslib": "^1.9.0",
43
+    "uuid": "^3.3.2",
44
+    "zone.js": "~0.10.1"
45
+  },
46
+  "devDependencies": {
47
+    "@angular-builders/custom-webpack": "^8.2.0",
48
+    "@angular-builders/dev-server": "^7.3.1",
49
+    "@angular-devkit/build-angular": "^0.802.2",
50
+    "@angular/cli": "^8.2.2",
51
+    "@angular/compiler-cli": "~8.2.1",
52
+    "@angular/language-service": "~8.2.1",
53
+    "@types/jasmine": "~3.4.0",
54
+    "@types/jasminewd2": "~2.0.3",
55
+    "@types/node": "^12.7.1",
56
+    "@types/systemjs": "^0.20.6",
57
+    "codelyzer": "^5.0.0",
58
+    "jasmine-core": "~3.4.0",
59
+    "jasmine-spec-reporter": "~4.2.1",
60
+    "karma": "~4.2.0",
61
+    "karma-chrome-launcher": "~3.0.0",
62
+    "karma-coverage-istanbul-reporter": "~2.1.0",
63
+    "karma-jasmine": "~2.0.1",
64
+    "karma-jasmine-html-reporter": "^1.4.0",
65
+    "protractor": "~5.4.0",
66
+    "ts-node": "~8.3.0",
67
+    "tslint": "~5.18.0",
68
+    "typescript": "~3.5.3"
69
+  }
70
+}

+ 9
- 0
src/frontend/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
+}

+ 69
- 0
src/frontend/src/app/apiclient/.drone.yml View File

@@ -0,0 +1,69 @@
1
+kind: pipeline
2
+name: default
3
+
4
+steps:
5
+- name: restore cache
6
+  image: drillster/drone-volume-cache
7
+  settings:
8
+    restore: true
9
+    mount:
10
+      - ./node_modules
11
+  volumes:
12
+  - name: cache
13
+    path: /cache
14
+    
15
+- name: npm install
16
+  image: node:12
17
+  commands:
18
+  - npm install
19
+
20
+- name: npm run build
21
+  image: node:12
22
+  commands:
23
+  - npm run build
24
+
25
+- name: rebuild cache
26
+  image: drillster/drone-volume-cache
27
+  settings:
28
+    rebuild: true
29
+    mount:
30
+      - ./node_modules
31
+  volumes:
32
+  - name: cache
33
+    path: /cache
34
+
35
+- name: npm publish
36
+  image: plugins/npm
37
+  settings:
38
+    username: frontblock
39
+    password:
40
+      from_secret: npm_password
41
+    email: frontblock.me@gmail.com
42
+  event:
43
+    - tag
44
+
45
+- name: deploy plugin
46
+  image: node:12
47
+  commands:
48
+  - git config --global user.email "${DRONE_COMMIT_AUTHOR_EMAIL}"
49
+  - git config --global user.name "${DRONE_COMMIT_AUTHOR}"
50
+  - git clone https://gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git
51
+  - cp -r ./dist/* ./${DRONE_REPO_NAME}
52
+  - cd ./${DRONE_REPO_NAME}
53
+  - git add -A
54
+  - git commit --allow-empty -m "drone tagged as version ${DRONE_TAG}"
55
+  - git tag ${DRONE_TAG}
56
+  - git push https://$GIT_USER:$GIT_PASSWORD@gitea.frontblock.me/fb-dist/${DRONE_REPO_NAME}.git master ${DRONE_TAG}
57
+  environment:
58
+    GIT_USER:
59
+      from_secret: git_user
60
+    GIT_PASSWORD:
61
+      from_secret: git_password
62
+  when:
63
+    event:
64
+    - tag
65
+
66
+volumes:
67
+- name: cache 
68
+  host:
69
+    path: /tmp

+ 10
- 0
src/frontend/src/app/apiclient/.gitignore View File

@@ -0,0 +1,10 @@
1
+dist
2
+.rpt2_cache
3
+node_modules
4
+lib
5
+
6
+*.d.ts
7
+*.js
8
+*.ts
9
+
10
+!src/**/*

+ 2
- 0
src/frontend/src/app/apiclient/.npmignore View File

@@ -0,0 +1,2 @@
1
+**/*
2
+!FrontblockApiClient.*

+ 17
- 0
src/frontend/src/app/apiclient/conf/ApiClient.json View File

@@ -0,0 +1,17 @@
1
+{
2
+  "apiConf": {
3
+    "apiHost": "api.testnet.frontblock.me",
4
+    "apiKey": "",
5
+    "apiPort": 10001,
6
+    "tls": false
7
+  },
8
+  "dbConf": {
9
+    "type": "sqlite",
10
+    "database": "./data/ApiClient.sqlite",
11
+    "synchronize": true,
12
+    "entities": [
13
+      null,
14
+      null
15
+    ]
16
+  }
17
+}

+ 4652
- 0
src/frontend/src/app/apiclient/package-lock.json
File diff suppressed because it is too large
View File


+ 45
- 0
src/frontend/src/app/apiclient/package.json View File

@@ -0,0 +1,45 @@
1
+{
2
+  "name": "frontblock",
3
+  "version": "0.14.0",
4
+  "description": "frontblock shop-side library ",
5
+  "main": "FrontblockApiClient.js",
6
+  "scripts": {
7
+    "tsc": "tsc",
8
+    "build-backend": "webpack --config src/backend/webpack.prod.js --progress --colors",
9
+    "build-frontend": "webpack --config src/frontend/webpack.prod.js --progress --colors",
10
+    "build": "npm run clean; npm run tsc; npm run build-backend; npm run build-frontend",
11
+    "clean": "rm -rf *.js *.ts backend frontend .rpt2_cache dist lib",
12
+    "update-frontblock": "rm -rf node_modules/frontblock*; npm install"
13
+  },
14
+  "keywords": [
15
+    "api",
16
+    "client",
17
+    "frontblock"
18
+  ],
19
+  "author": "",
20
+  "license": "ISC",
21
+  "dependencies": {
22
+    "@angular/common": "^8.2.1",
23
+    "@angular/core": "^8.2.1",
24
+    "@angular/forms": "^8.2.1",
25
+    "@angular/platform-browser": "^8.2.1",
26
+    "@angular/router": "^8.2.1",
27
+    "@clr/angular": "^2.1.1",
28
+    "@types/node": "^11.13.10",
29
+    "frontblock-generic": "^0.28.4",
30
+    "fs": "0.0.1-security",
31
+    "knex": "^0.19.2",
32
+    "log4js": "^4.3.1",
33
+    "node-fetch": "^2.5.0",
34
+    "path": "^0.12.7",
35
+    "rxjs": "^6.5.2",
36
+    "rxjs-compat": "^6.5.2"
37
+  },
38
+  "devDependencies": {
39
+    "ts-loader": "^6.0.4",
40
+    "typescript": "^3.4.5",
41
+    "webpack": "^4.39.1",
42
+    "webpack-cli": "^3.3.6",
43
+    "webpack-node-externals": "^1.7.2"
44
+  }
45
+}

+ 104
- 0
src/frontend/src/app/apiclient/src/backend/FrontblockApiClient.ts View File

@@ -0,0 +1,104 @@
1
+import { Coin, AccountMap, TransactionMap, SubscriptionResponse, ErrorResponse, SuccessResponse, parseResponse, parseSubResponse } from "frontblock-generic/Types";
2
+import { FrontblockApi as FrontblockApi } from "frontblock-generic/Api";
3
+import * as Logger from 'log4js'
4
+
5
+Logger.configure({
6
+    appenders: {
7
+        "frontblock-api-client": { type: 'stdout' },
8
+        //app: { type: 'file', filename: 'application.log' }
9
+    },
10
+    categories: {
11
+        default: { appenders: ['frontblock-api-client'], level: 'debug' }
12
+    }
13
+})
14
+const logger = Logger.getLogger("frontblock-api-client")
15
+const bsock = require('bsock')
16
+
17
+export type FrontblockApiConf = {
18
+    apiHost: string
19
+    apiPort: number
20
+    tls?: boolean
21
+    apiKey?: string
22
+}
23
+
24
+
25
+/**
26
+ * Frontblock api connection lib
27
+ */
28
+export class FrontblockApiClient implements FrontblockApi {
29
+    protected started: boolean = false
30
+    protected apikey: string = ""
31
+    protected socket
32
+
33
+    constructor(private conf: FrontblockApiConf){
34
+        
35
+    }
36
+
37
+    async consume<C extends Coin>(apikey: string, uid: string, callback: (tx: TransactionMap[C]) => void): Promise<SubscriptionResponse | ErrorResponse> {
38
+        const r = await this.socket.call("consume", apikey, uid)
39
+        const res = parseSubResponse(r)
40
+        if (res instanceof SubscriptionResponse) {
41
+            this.socket.hook(res.uid, (tx) => {
42
+                const deserialized = <TransactionMap[C]>JSON.parse(tx)
43
+                callback(deserialized)
44
+            })
45
+        }
46
+        return res
47
+    }
48
+
49
+    async quit(apikey: string, consumerUid: string): Promise<ErrorResponse | SuccessResponse> {
50
+
51
+        const r = await this.socket.call("quit", apikey, consumerUid)
52
+        const res = parseResponse(r)
53
+        return res
54
+    }
55
+
56
+    async subscribe<C extends Coin>(apikey: string, coin: C, account: AccountMap[C]): Promise<SubscriptionResponse | ErrorResponse> {
57
+        const r = await this.socket.call("subscribe", apikey, coin, account)
58
+        const res = parseSubResponse(r)
59
+        return res
60
+    }
61
+
62
+    async subsume<C extends Coin>(apikey: string, coin: C, account: AccountMap[C], callback: (tx: TransactionMap[C]) => void): Promise<SubscriptionResponse | ErrorResponse> {
63
+        const r = await this.socket.call("subsume", apikey, coin, account)
64
+        const res = parseSubResponse(r)
65
+        if (res instanceof SubscriptionResponse) {
66
+            this.socket.hook(res.uid, (tx) => {
67
+                const deserialized = <TransactionMap[C]>JSON.parse(tx)
68
+                callback(deserialized)
69
+            })
70
+        }
71
+        return res
72
+    }
73
+
74
+    async unsubscribe(apikey: string, uid: string): Promise<ErrorResponse | SuccessResponse> {
75
+        const r = await this.socket.call('unsubscribe', apikey, uid)
76
+        const res = parseResponse(r)
77
+        if (res instanceof SuccessResponse)
78
+            this.socket.unhook(uid)
79
+        return res
80
+    }
81
+
82
+    async getPluginList(): Promise<string[]> {
83
+        return await this.socket.call('getPluginList')
84
+    }
85
+
86
+
87
+    connect(): void {
88
+        if (this.started) {
89
+            logger.warn("FrontblockApiClient has already been started. Ignoring")
90
+            return
91
+        }
92
+        logger.info("Starting apiClient with ", this.conf)
93
+        this.socket = bsock.connect(this.conf.apiPort, this.conf.apiHost, this.conf.tls != null ? this.conf.tls : false)
94
+        this.started = true
95
+    }
96
+
97
+    disconnect(): void {
98
+        if (!this.started) {
99
+            logger.warn("FrontblockApiClient has not been started. Ignoring")
100
+            return
101
+        }
102
+        this.socket.close()
103
+    }
104
+}

+ 201
- 0
src/frontend/src/app/apiclient/src/backend/Plugin.ts View File

@@ -0,0 +1,201 @@
1
+import { FrontblockApiClient, FrontblockApiConf } from "./FrontblockApiClient";
2
+import { socketioRPC } from "frontblock-generic/RPC"
3
+import { DatabasePlugin, TableDefiniton } from "frontblock-generic/DatabasePlugin"
4
+
5
+import { Coin, AccountMap, TransactionMap, SubscriptionResponse } from "frontblock-generic/Types";
6
+import * as knex from 'knex';
7
+import * as Logger from 'log4js'
8
+Logger.configure({
9
+    appenders: {
10
+        "frontblock-api-client": { type: 'stdout' },
11
+        //app: { type: 'file', filename: 'application.log' }
12
+      },
13
+      categories: {
14
+        default: { appenders: [ 'frontblock-api-client' ], level: 'debug' }
15
+      }
16
+})
17
+const logger = Logger.getLogger("frontblock-api-client") 
18
+
19
+/**
20
+ * Frontblock admin Plugin for FrontblockApiClient
21
+ */
22
+
23
+type ApiClientPluginConf = { 
24
+    apiConf: FrontblockApiConf,
25
+}
26
+
27
+export default class FrontblockApiClientPlugin extends DatabasePlugin<ApiClientPluginConf>{
28
+    
29
+    private apiClient : FrontblockApiClient
30
+    constructor(){
31
+        super("ApiClient")
32
+    }
33
+
34
+    getDefaultConfig(): ApiClientPluginConf & { dbConf: knex.Config; } {
35
+        return {
36
+            apiConf: {
37
+                apiHost: "api.testnet.frontblock.me",
38
+                apiKey: "",
39
+                apiPort: 10001,
40
+                tls: false
41
+            },
42
+            dbConf: {
43
+                client: 'sqlite3',
44
+                connection: {
45
+                    filename: "./data/ApiClient.sqlite"
46
+                },
47
+                useNullAsDefault: true
48
+            }
49
+        }
50
+    }
51
+
52
+    async getConsumers(): Promise<SubscriptionResponse[]> {
53
+        const records = await this.knex.select('*').from('consumers')
54
+        return records.map(r => JSON.parse(r.JSON))
55
+    }
56
+
57
+    async getSubscriptions(): Promise<SubscriptionResponse[]> {
58
+        const records = await this.knex.select('*').from('subscriptions')
59
+        return records.map(r => JSON.parse(r.JSON))
60
+    }
61
+
62
+    exportExtraRPCs(): socketioRPC[] {
63
+        const quit = async(uid:string) => {return await this.quit(this.getConfigKey('apiConf').apiKey, uid) }
64
+        return [
65
+            {
66
+                name: "subscribe",
67
+                func: async <C extends Coin>(coin: C, account: AccountMap[C]) => { return await this.subscribe(this.getConfigKey('apiConf').apiKey, coin, account)},
68
+                type: 'call',
69
+                visibility: 'private'
70
+            },{
71
+                name: "subsume",
72
+                func: async <C extends Coin>(coin: C, account: AccountMap[C], callback: (tx: TransactionMap[C]) => void) => { return await this.subsume(this.getConfigKey('apiConf').apiKey, coin, account, callback)},
73
+                type: 'hook',
74
+                unhook: quit,
75
+                visibility: 'private'
76
+            },{
77
+                name: 'unsubscribe',
78
+                func: async(uid:string) => { return await this.unsubscribe(this.getConfigKey('apiConf').apiKey, uid) },
79
+                type: 'call',
80
+                visibility: 'private'
81
+            },{
82
+                name: 'consume',
83
+                func: async<C extends Coin>(uid: string, callback: (tx: TransactionMap[C]) => void) => {return await this.consume(this.getConfigKey('apiConf').apiKey, uid, callback)},
84
+                type: 'hook',
85
+                unhook: quit,
86
+                visibility: 'private'
87
+            },{
88
+                name: 'quit',
89
+                func: quit,
90
+                type: 'call',
91
+                visibility: 'private'
92
+            },{
93
+                name: "getSubscriptions",
94
+                func: async() => { return await this.getSubscriptions() },
95
+                type: "call",
96
+                visibility: "private"
97
+            },{
98
+                name: "getConsumers",
99
+                func: async() => { return await this.getConsumers() },
100
+                type: "call",
101
+                visibility: "private"
102
+            }
103
+        ]
104
+    }
105
+
106
+    private subscribe:FrontblockApiClient['subscribe'] = async (apiKey, coin, account) => {
107
+        const res = await this.apiClient.subscribe(apiKey, coin, account)
108
+        if(res instanceof SubscriptionResponse){
109
+            await this.knex('subscriptions').insert([{uuid: res.uid, JSON: JSON.stringify(res)}])
110
+        }
111
+        return res
112
+    }
113
+
114
+    private consume:FrontblockApiClient['consume'] = async (apikey, uuid, callback) => {
115
+        const res = await this.apiClient.consume(apikey, uuid, callback)
116
+        if(res instanceof SubscriptionResponse){
117
+            await this.knex('consumers').insert([{uuid: res.uid, JSON: JSON.stringify(res)}])
118
+        }
119
+        return res
120
+    }
121
+
122
+    private unsubscribe:FrontblockApiClient['unsubscribe'] = async (apikey, uuid) => {
123
+        const res = await this.apiClient.unsubscribe(apikey, uuid)
124
+        await this.knex('subscriptions')
125
+        .where('uuid', uuid)
126
+        .del()
127
+        return res
128
+    }
129
+
130
+    private quit:FrontblockApiClient['quit'] = async (apikey, uuid) => {
131
+        const res = await this.apiClient.quit(apikey, uuid)
132
+        await this.knex('consumers')
133
+        .where('uuid', uuid)
134
+        .del()
135
+        return res    
136
+    }
137
+
138
+    private subsume:FrontblockApiClient['subsume'] = async (apikey, coin, account, callback) => {
139
+        const res = await this.apiClient.subsume(apikey, coin, account, callback)
140
+        if(res instanceof SubscriptionResponse){
141
+            await Promise.all([
142
+                this.knex('consumers')
143
+                .insert([{uuid: res.uid, JSON: JSON.stringify(res)}]),
144
+                
145
+                this.knex('subscriptions')
146
+                .insert([{uuid: res.uid, JSON: JSON.stringify(new SubscriptionResponse(res.message))}])
147
+            ])
148
+        }
149
+        return res
150
+    }
151
+
152
+    private makeApiClient(conf: FrontblockApiConf):FrontblockApiClient{
153
+        if(this.apiClient)
154
+            this.apiClient.disconnect()
155
+        this.apiClient = new FrontblockApiClient(conf)
156
+        this.apiClient.connect()
157
+        return this.apiClient
158
+    }
159
+
160
+    setConfig(conf:ApiClientPluginConf & { dbConf: knex.Config }):ApiClientPluginConf & { dbConf: knex.Config }{
161
+        const currConf = this.getConfig()
162
+        if(JSON.stringify(currConf.apiConf) !== JSON.stringify(conf.apiConf)){
163
+            this.makeApiClient(conf.apiConf)
164
+        }
165
+        return super.setConfig(conf)
166
+    }
167
+
168
+    setConfigKey(key:keyof ApiClientPluginConf, value: any):ApiClientPluginConf & { dbConf: knex.Config; }{
169
+        if(key === 'apiConf'){
170
+            this.makeApiClient(value)
171
+        }
172
+        return super.setConfigKey(key, value)
173
+    }
174
+
175
+
176
+    async start(): Promise<void> {
177
+        this.makeApiClient(this.getConfig().apiConf)
178
+        super.start()
179
+    }
180
+
181
+    stop(): void {
182
+        this.apiClient.disconnect()
183
+        super.stop()
184
+    }
185
+
186
+    protected getTableDefinitions(): TableDefiniton[] {
187
+        return [{
188
+            name: 'subscriptions', 
189
+            tableBuilder: function (table) {
190
+                table.string('uuid').primary();
191
+                table.string('JSON');
192
+            }
193
+        },{
194
+            name: 'consumers', 
195
+            tableBuilder: function (table) {
196
+                table.string('uuid').primary();
197
+                table.string('JSON');
198
+            }
199
+        }]
200
+    }
201
+}

+ 38
- 0
src/frontend/src/app/apiclient/src/backend/webpack.prod.js View File

@@ -0,0 +1,38 @@
1
+const nodeExternals = require('webpack-node-externals');
2
+const TerserPlugin = require('terser-webpack-plugin');
3
+var webpack = require('webpack');
4
+const path = require('path');
5
+
6
+module.exports = {
7
+  mode: 'production',
8
+  target: "node",
9
+  entry: path.resolve(__dirname, 'Plugin.ts'),
10
+  output: {
11
+      path: path.resolve(__dirname, '../../dist'),
12
+      filename: 'Plugin.js',
13
+      libraryTarget: 'commonjs',
14
+  },
15
+  resolve: {
16
+    // Add `.ts` and `.tsx` as a resolvable extension.
17
+
18
+    extensions: [".ts", ".tsx", ".js"]
19
+  },
20
+  module: {
21
+    rules: [
22
+      { test: /\.ts?$/, loader: "ts-loader" }
23
+    ]
24
+  },
25
+  optimization: {
26
+    minimize: false
27
+  },
28
+  node: {
29
+    global: true,
30
+    process: true,
31
+    __filename: false,
32
+    __dirname: false,
33
+    Buffer: true,
34
+  },
35
+  externals:{
36
+    knex: "../node_modules/knex"
37
+  }
38
+}

+ 92
- 0
src/frontend/src/app/apiclient/src/frontend/apiclient-consumptions.component.ts View File

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

+ 134
- 0
src/frontend/src/app/apiclient/src/frontend/apiclient-settings-form.component.ts View File

@@ -0,0 +1,134 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { FrontblockApiConf } from '../backend/FrontblockApiClient';
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
+    <knex-config fbPlugin="ApiClient"></knex-config>
66
+  `
67
+})
68
+export class ApiclientFormComponent implements OnInit {
69
+    testnet: boolean = true
70
+    saving: boolean = false
71
+    advanced: boolean = false
72
+    data: FrontblockApiConf = {
73
+        apiHost: "api.testnet.frontblock.me",
74
+        apiPort: 10001,
75
+        tls: false,
76
+        apiKey: ""
77
+    }
78
+
79
+    constructor() { }
80
+
81
+    ngOnInit() { }
82
+
83
+    setAdvanced() { this.advanced = true }
84
+
85
+    checkData(): boolean {
86
+        return (
87
+            typeof this.data.apiPort === "number" &&
88
+            typeof this.data.apiHost === "string" &&
89
+            typeof this.data.tls === "boolean" &&
90
+            typeof this.data.apiKey === "string"
91
+        )
92
+    }
93
+
94
+    updateTestnet() {
95
+        if (this.testnet) {
96
+            this.data.apiHost = "api.testnet.frontblock.me"
97
+            this.data.apiPort = 10001
98
+        } else {
99
+            this.data.apiHost = "api.frontblock.me"
100
+            this.data.apiPort = 10000
101
+        }
102
+    }
103
+
104
+    updatePort(obj) {
105
+        switch (this.data.apiHost) {
106
+            case "api.testnet.frontblock.me":
107
+                this.data.apiPort = 10001
108
+                break;
109
+            case "api.frontblock.me":
110
+                this.data.apiPort = 10000
111
+                break;
112
+
113
+            default:
114
+                console.warn("A non-standard api host was chosen: " + this.data.apiPort)
115
+                break;
116
+        }
117
+        console.log(this.data)
118
+    }
119
+
120
+    async save() {
121
+        this.saving = true;
122
+        if (typeof this.data.apiPort === "string")
123
+            this.data.apiPort = parseInt(this.data.apiPort)
124
+
125
+        if (this.checkData()) {
126
+            const conf = await fb.ApiClient.setConfig(this.data)
127
+            console.log(conf)
128
+        } else {
129
+            //show error in gui
130
+            console.error("bad data :(")
131
+        }
132
+        this.saving = false
133
+    }
134
+}

+ 101
- 0
src/frontend/src/app/apiclient/src/frontend/apiclient-subscriptions.component.ts View File

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

+ 52
- 0
src/frontend/src/app/apiclient/src/frontend/module.ts View File

@@ -0,0 +1,52 @@
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, SidebarEntries } from 'frontblock-generic/Plugin';
10
+import { ClarityModule } from '@clr/angular';
11
+import { FormsModule } from '@angular/forms';
12
+// @ts-ignore
13
+import { KnexConfigModule } from "../../../knex-config/knex-config.module";
14
+
15
+@NgModule({
16
+  imports: [
17
+    FormsModule,
18
+    ClarityModule,
19
+    KnexConfigModule,
20
+    CommonModule, 
21
+    RouterModule.forChild([
22
+      {path: "settings", component: ApiclientFormComponent},
23
+      {path: "subscriptions", component: ApiclientSubscriptionComponent},
24
+      {path: "consumers", component: ApiclientConsumptionComponent},
25
+    ]),
26
+  ],
27
+  exports: [RouterModule],
28
+  declarations: [
29
+    ApiclientFormComponent,
30
+    ApiclientConsumptionComponent,
31
+    ApiclientSubscriptionComponent
32
+  ]
33
+})
34
+export class PluginModule implements FrontendPlugin{
35
+  getSidebarEntry(): SidebarEntries {
36
+    return {
37
+      icon: "terminal",
38
+      parentRoute: "apiclient",
39
+      text: "Api client",
40
+      links: [{
41
+        route: "settings",
42
+        text: "Settings"
43
+      },{
44
+        route: "consumers",
45
+        text: "Consumers"
46
+      },{
47
+        route: "subscriptions",
48
+        text: "Subscriptions"
49
+      },]
50
+    }
51
+  }
52
+}

+ 41
- 0
src/frontend/src/app/apiclient/src/frontend/webpack.prod.js View File

@@ -0,0 +1,41 @@
1
+const path = require('path');
2
+const TerserPlugin = require('terser-webpack-plugin');
3
+
4
+module.exports = {
5
+  mode: 'production',
6
+  target: "web",
7
+  entry: path.resolve(__dirname, 'module.ts'),
8
+  output: {
9
+      path: path.resolve(__dirname, '../../dist'),
10
+      filename: 'FrontendPlugin.js',
11
+      libraryTarget: 'commonjs',
12
+  },
13
+  resolve: {
14
+    // Add `.ts` and `.tsx` as a resolvable extension.
15
+    extensions: [".ts", ".tsx", ".js"]
16
+  },
17
+  module: {
18
+    rules: [
19
+      { test: /\.ts?$/, loader: "ts-loader" }
20
+    ]
21
+  },
22
+  optimization: {
23
+    minimizer: [
24
+      new TerserPlugin({
25
+        exclude: [
26
+          /\.\/(.*)\/.ts/,
27
+          /\.\/(.*).ts/,
28
+        ],
29
+      }),
30
+    ],
31
+  },
32
+  externals: {
33
+    '@angular/core': '@angular/core',
34
+    '@angular/common': '@angular/common',
35
+    '@angular/router': '@angular/router',
36
+    '@angular/animations': '@angular/animations',
37
+    '@angular/forms': '@angular/forms',
38
+    '@clr/angular': '@clr/angular',
39
+    '../../../knex-config/knex-config.module': 'knexconfig'
40
+  }
41
+}

+ 10
- 0
src/frontend/src/app/apiclient/tsconfig.frontend.json View File

@@ -0,0 +1,10 @@
1
+{
2
+    "extends": "./tsconfig.json",
3
+    "include": ["src/frontend"],
4
+    "exclude": [
5
+        "src/backend"
6
+    ],
7
+    "compilerOptions": {
8
+        "sourceMap": true
9
+    }
10
+}

+ 18
- 0
src/frontend/src/app/apiclient/tsconfig.json View File

@@ -0,0 +1,18 @@
1
+{
2
+  "compilerOptions": {
3
+    "allowSyntheticDefaultImports": true,
4
+    "alwaysStrict": false,
5
+    "noImplicitAny": false,
6
+    "target": "ES2015",
7
+    "module": "commonjs",
8
+    "declaration": true,
9
+    "strict": true,
10
+    "outDir": ".",
11
+    "experimentalDecorators": true,
12
+    "strictPropertyInitialization": false,
13
+    "importHelpers": true,
14
+    "strictNullChecks": false,
15
+  },
16
+  "include": ["src/backend"],
17
+  "exclude": []
18
+}

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

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

+ 5
- 0
src/frontend/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/frontend/src/app/app.component.scss View File


+ 35
- 0
src/frontend/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/frontend/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/dev/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/frontend/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/frontend/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/frontend/src/app/content-area/main.component.scss View File


+ 25
- 0
src/frontend/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/frontend/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/frontend/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/frontend/src/app/dynamic-loader/dynamic-loader.component.scss View File

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

+ 25
- 0
src/frontend/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
+});

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

@@ -0,0 +1,133 @@
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
+import * as btcHdkey from "btc-hdkey"
22
+import * as bitcoinjslib from "bitcoinjs-lib"
23
+import * as fetch from "node-fetch"
24
+import * as coinselect from "coinselect/accumulative"
25
+import * as KnexModule from '../knex-config/knex-config.module'
26
+
27
+SystemJS.set('@clr/angular', SystemJS.newModule(clarityModule));
28
+SystemJS.set('@angular/router', SystemJS.newModule(angularRouter));
29
+SystemJS.set('@angular/core', SystemJS.newModule(angularCore));
30
+SystemJS.set('@angular/common', SystemJS.newModule(angularCommon));
31
+SystemJS.set('@angular/common/http', SystemJS.newModule(angularCommonHttp));
32
+SystemJS.set('@angular/forms', SystemJS.newModule(angularForms));
33
+SystemJS.set('@angular/animations', SystemJS.newModule(angularAnimations));
34
+SystemJS.set('@angular/animations/browser', SystemJS.newModule(angularAnimationsBrowser));
35
+SystemJS.set('@angular/platform-browser', SystemJS.newModule(angularPlatformBrowser));
36
+SystemJS.set('@angular/platform-browser/animations', SystemJS.newModule(browserAnimationsModule));
37
+SystemJS.set('@angular/platform-browser-dynamic', SystemJS.newModule(angularPlatformBrowserDynamic));
38
+SystemJS.set('frontblock-generic/Types', SystemJS.newModule(frontblockGenericTypes));
39
+SystemJS.set('btc-hdkey', SystemJS.newModule(btcHdkey))
40
+SystemJS.set('bitcoinjs-lib', SystemJS.newModule(bitcoinjslib))
41
+SystemJS.set('node-fetch', SystemJS.newModule(fetch))
42
+SystemJS.set('coinselect/accumulative', SystemJS.newModule(coinselect))
43
+SystemJS.set('knexconfig', SystemJS.newModule(KnexModule))
44
+
45
+SystemJS.config({ meta: { '*': { authorization: true } } });
46
+/** --------- */
47
+
48
+import { AfterViewInit, Component, ViewChild } from '@angular/core';
49
+import { SidebarComponent } from '../sidebar/sidebar.component';
50
+import { Router } from '@angular/router';
51
+import { FrontendPlugin, SidebarEntries, SidebarEntry } from "frontblock-generic/Plugin"
52
+import { environment } from "../../environments/environment"
53
+
54
+const fb = environment.production ? window["fb"] : {
55
+  UpdateManager: {
56
+    getLoadedPluginNames: () => ["ApiClient", "Wallet"]
57
+  }
58
+}
59
+
60
+@Component({
61
+  selector: 'dynamic-loader',
62
+  templateUrl: './dynamic-loader.component.html',
63
+  styleUrls: ['./dynamic-loader.component.scss']
64
+})
65
+export class DynamicLoaderComponent implements AfterViewInit {
66
+  @ViewChild(SidebarComponent, { static: false })
67
+  private sidebar: SidebarComponent
68
+
69
+  constructor(
70
+    private router: Router,
71
+  ) {
72
+  }
73
+
74
+  ngAfterViewInit() {
75
+    console.log("env", environment)
76
+
77
+    this.loadWidgets()
78
+  }
79
+
80
+  private async loadWidgets() {
81
+    while (!fb.UpdateManager) {
82
+      await new Promise((resolve) => setTimeout(resolve, 50))
83
+    }
84
+    const pluginNames = await fb.UpdateManager.getLoadedPluginNames()
85
+    pluginNames.forEach(element => this.installWidget(element));
86
+  }
87
+
88
+  private async installWidget(pluginName: string) {
89
+    let module;
90
+    if (!angularCore.isDevMode()){
91
+      module = await SystemJS.import("plugins/" + pluginName + ".js");
92
+    }else{
93
+      if(environment.loadLocal){
94
+        console.log("Loading "+pluginName+" from angular tsc-out")
95
+        module = await import("../"+pluginName.toLocaleLowerCase()+"/src/frontend/module")
96
+      }else{
97
+        console.log("Loading "+pluginName+" from fake backend")
98
+        module = await SystemJS.import("assets/" + pluginName.toLowerCase() + ".js")
99
+      }
100
+    }
101
+
102
+    const plugin:FrontendPlugin = new module['PluginModule']()
103
+    const entry: SidebarEntries | SidebarEntry = plugin.getSidebarEntry()
104
+    const rc = this.router.config
105
+
106
+    switch (typeof (<any>entry).links) {
107
+      case "undefined":
108
+        const e: SidebarEntry = <SidebarEntry>entry
109
+        e.route = "plugin/" + e.route
110
+
111
+        this.sidebar.entries.push(e)
112
+        rc.unshift({
113
+          path: "plugin",
114
+          loadChildren: async () => module['PluginModule']
115
+        })
116
+
117
+        break;
118
+      case "object":
119
+        const m: SidebarEntries = <SidebarEntries>entry
120
+
121
+        m.links = m.links.map(link => {
122
+          return { text: link.text, route: m.parentRoute + "/" + link.route }
123
+        })
124
+
125
+        this.sidebar.multientires.push(m)
126
+        rc.unshift({
127
+          path: m.parentRoute,
128
+          loadChildren: async () => module['PluginModule']
129
+        })
130
+    }
131
+    this.router.resetConfig(rc)
132
+  }
133
+}

+ 15
- 0
src/frontend/src/app/error-display/a-nested.component.ts View File

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'exclamations',
5
+  template: `
6
+  <span style="color: blue">!!!</span>
7
+  `
8
+})
9
+export class ANestedComponent implements OnInit {
10
+
11
+  constructor() { }
12
+
13
+  ngOnInit() { }
14
+
15
+}

+ 1
- 0
src/frontend/src/app/error-display/error-display.component.html View File

@@ -0,0 +1 @@
1
+<p>Oops that didn't work!</p>

+ 0
- 0
src/frontend/src/app/error-display/error-display.component.scss View File


+ 25
- 0
src/frontend/src/app/error-display/error-display.component.spec.ts View File

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

+ 15
- 0
src/frontend/src/app/error-display/error-display.component.ts View File

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-error-display',
5
+  templateUrl: './error-display.component.html',
6
+  styleUrls: ['./error-display.component.scss'],
7
+})
8
+export class ErrorDisplayComponent implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 22
- 0
src/frontend/src/app/error-display/module.ts View File

@@ -0,0 +1,22 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { ErrorDisplayComponent } from './error-display.component';
4
+import { ANestedComponent } from './a-nested.component';
5
+import { RouterModule } from '@angular/router';
6
+
7
+
8
+@NgModule({
9
+  imports: [CommonModule, RouterModule.forChild([{path: "helloWorld", component: ErrorDisplayComponent}])],
10
+  exports: [RouterModule],
11
+  declarations: [
12
+    ErrorDisplayComponent,
13
+    ANestedComponent
14
+  ],
15
+  entryComponents: [ErrorDisplayComponent],
16
+  providers: [{
17
+    provide: 'provider',
18
+    useValue: ErrorDisplayComponent
19
+  }],
20
+  
21
+})
22
+export class PluginModule { }

+ 12
- 0
src/frontend/src/app/header-bar/header-bar.component.html View File

@@ -0,0 +1,12 @@
1
+<clr-header class="header header-1">
2
+    <div class="branding">
3
+        <span class="title">Frontblock</span>
4
+        
5
+    </div>
6
+    <div class="header-actions">
7
+        <a class="nav-link nav-icon-text">
8
+            <clr-icon shape="bug"></clr-icon> 
9
+            <span class="nav-text">ALPHA {{devmode}}</span>
10
+        </a>
11
+    </div>
12
+</clr-header>

+ 0
- 0
src/frontend/src/app/header-bar/header-bar.component.scss View File


+ 25
- 0
src/frontend/src/app/header-bar/header-bar.component.spec.ts View File

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

+ 15
- 0
src/frontend/src/app/header-bar/header-bar.component.ts View File

@@ -0,0 +1,15 @@
1
+import { Component, OnInit, isDevMode } from '@angular/core';
2
+import { environment } from '../../environments/environment'
3
+@Component({
4
+  selector: 'header-bar',
5
+  templateUrl: './header-bar.component.html',
6
+  styleUrls: ['./header-bar.component.scss']
7
+})
8
+export class HeaderBarComponent implements OnInit {
9
+  devmode = isDevMode()?(environment.loadLocal?"{ ng-DEV }":"{ ng-PRODLIKE }"):""
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 1
- 0
src/frontend/src/app/home/home.component.html View File

@@ -0,0 +1 @@
1
+<p>home works!</p>

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


+ 25
- 0
src/frontend/src/app/home/home.component.spec.ts View File

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

+ 15
- 0
src/frontend/src/app/home/home.component.ts View File

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'app-home',
5
+  templateUrl: './home.component.html',
6
+  styleUrls: ['./home.component.scss']
7
+})
8
+export class HomeComponent implements OnInit {
9
+
10
+  constructor() { }
11
+
12
+  ngOnInit() {
13
+  }
14
+
15
+}

+ 15
- 0
src/frontend/src/app/htmlsupplier/a-nested.component.ts View File

@@ -0,0 +1,15 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+@Component({
4
+  selector: 'exclamations',
5
+  template: `
6
+  <span style="color: blue">!!!</span>
7
+  `
8
+})
9
+export class ANestedComponent implements OnInit {
10
+
11
+  constructor() { }
12
+
13
+  ngOnInit() { }
14
+
15
+}

+ 24
- 0
src/frontend/src/app/htmlsupplier/component.ts View File

@@ -0,0 +1,24 @@
1
+import { Component, OnInit } from '@angular/core';
2
+declare const fb
3
+@Component({
4
+  selector: 'HTMLSUPPLIER', //!!!!
5
+  template: `
6
+  <div class="card">
7
+    <div class="card-block">
8
+      <div class="card-title">
9
+      HTMLSUPPLIER <br> ${Object.keys(fb.HtmlSupplier).join("<br>")}
10
+      </div>
11
+      <div class="card-text">
12
+        Hello World <exclamations></exclamations>
13
+      </div>
14
+    </div>
15
+  </div>
16
+  `
17
+})
18
+export class PluginComponent implements OnInit {
19
+
20
+  constructor() { }
21
+
22
+  ngOnInit() { }
23
+
24
+}

+ 21
- 0
src/frontend/src/app/htmlsupplier/module.ts View File

@@ -0,0 +1,21 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { PluginComponent } from './component';
4
+import { ANestedComponent } from './a-nested.component';
5
+import { RouterModule } from '@angular/router';
6
+import { FrontendPlugin, SidebarEntry } from 'frontblock-generic/Plugin';
7
+
8
+@NgModule({
9
+  imports: [CommonModule, RouterModule.forChild([{path: "htmlsupplier", component: PluginComponent}])],
10
+  exports: [RouterModule],
11
+  declarations: [
12
+    PluginComponent,
13
+    ANestedComponent
14
+  ],
15
+  entryComponents: [PluginComponent],
16
+  providers: [{
17
+    provide: 'provider',
18
+    useValue: PluginComponent
19
+  }]
20
+})
21
+export class PluginModule{}

+ 1
- 0
src/frontend/src/app/knex-config/knex-config.component.html View File

@@ -0,0 +1 @@
1
+<p>knex-config works!</p>

+ 0
- 0
src/frontend/src/app/knex-config/knex-config.component.scss View File


+ 25
- 0
src/frontend/src/app/knex-config/knex-config.component.spec.ts View File

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

+ 196
- 0
src/frontend/src/app/knex-config/knex-config.component.ts View File

@@ -0,0 +1,196 @@
1
+import { Component, OnInit, Input } from '@angular/core';
2
+
3
+declare const fb
4
+
5
+@Component({
6
+    selector: 'knex-config',
7
+    template: `
8
+    <div class="clr-row">
9
+        <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
10
+            <div class="card-header">
11
+            Database configuration
12
+            </div>
13
+            <div class="card-block">
14
+                <div class="card-text">
15
+                  <clr-tabs>
16
+                    <clr-tab>
17
+                      <button clrTabLink id="link1">mySQL</button>
18
+                      <ng-template [(clrIfActive)]="mode['mysql']">
19
+                        <clr-tab-content id="content1">
20
+                          <form clrForm clrLayout="horizontal">
21
+                            <clr-input-container >
22
+                              <label class="clr-col-12 clr-col-md-4">host</label>
23
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="mysql-host" [(ngModel)]="mysql['connection']['host']" placeholder="127.0.0.1" required />
24
+                            </clr-input-container>
25
+                            <clr-input-container >
26
+                              <label class="clr-col-12 clr-col-md-4">user</label>
27
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="mysql-user" [(ngModel)]="mysql['connection']['user']" placeholder="root" required />
28
+                            </clr-input-container>
29
+                            <clr-input-container >
30
+                              <label class="clr-col-12 clr-col-md-4">password</label>
31
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="password" name="mysql-password" [(ngModel)]="mysql['connection']['password']" placeholder="*****" required />
32
+                            </clr-input-container>
33
+                            <clr-input-container >
34
+                              <label class="clr-col-12 clr-col-md-4">database</label>
35
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="mysql-database" [(ngModel)]="mysql['connection']['database']" placeholder="db_name" required />
36
+                            </clr-input-container>
37
+                            <clr-input-container >
38
+                              <label class="clr-col-12 clr-col-md-4">version</label>
39
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="mysql-version" [(ngModel)]="mysql['version']" placeholder="5.7" required />
40
+                            </clr-input-container>
41
+                          </form>
42
+                        </clr-tab-content>
43
+                      </ng-template>
44
+                    </clr-tab>
45
+                    <clr-tab>
46
+                      <button clrTabLink>pgSQL</button>
47
+                      <ng-template [(clrIfActive)]="mode['pg']">
48
+                        <clr-tab-content>
49
+                          <form clrForm clrLayout="horizontal">
50
+                          <clr-input-container >
51
+                          <label class="clr-col-12 clr-col-md-4">host</label>
52
+                          <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="pg-host" [(ngModel)]="pg['connection']['host']" placeholder="127.0.0.1" required />
53
+                        </clr-input-container>
54
+                        <clr-input-container >
55
+                          <label class="clr-col-12 clr-col-md-4">user</label>
56
+                          <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="pg-host" [(ngModel)]="pg['connection']['user']" placeholder="root" required />
57
+                        </clr-input-container>
58
+                        <clr-input-container >
59
+                          <label class="clr-col-12 clr-col-md-4">password</label>
60
+                          <input class="clr-col-12 clr-col-md-8" clrInput type="password" name="pg-host" [(ngModel)]="pg['connection']['password']" placeholder="*****" required />
61
+                        </clr-input-container>
62
+                        <clr-input-container >
63
+                          <label class="clr-col-12 clr-col-md-4">database</label>
64
+                          <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="pg-host" [(ngModel)]="pg['connection']['database']" placeholder="db_name" required />
65
+                        </clr-input-container>
66
+                        <clr-input-container >
67
+                          <label class="clr-col-12 clr-col-md-4">version</label>
68
+                          <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="pg-host" [(ngModel)]="pg['version']" placeholder="7.2" required />
69
+                        </clr-input-container>
70
+                          </form>
71
+                        </clr-tab-content>
72
+                      </ng-template>
73
+                    </clr-tab>
74
+                    <clr-tab>
75
+                      <button clrTabLink>SQLite3</button>
76
+                      <ng-template [(clrIfActive)]="mode['sqlite3']">
77
+                        <clr-tab-content>
78
+                          <form clrForm clrLayout="horizontal">
79
+                            <clr-input-container >
80
+                              <label class="clr-col-12 clr-col-md-4">file</label>
81
+                              <input class="clr-col-12 clr-col-md-8" clrInput type="text" name="sqlite-file" [(ngModel)]="sqlite3['connection']['filename']" placeholder="./data/benis.sqlite" required />
82
+                            </clr-input-container>
83
+                          </form>
84
+                        </clr-tab-content>
85
+                      </ng-template>
86
+                    </clr-tab>
87
+                  </clr-tabs>
88
+                </div>
89
+            </div>
90
+            <div class="card-footer">
91
+                <button *ngIf="!saving" class="btn btn-success-outline" (click)="save()">
92
+                <span>Save</span>
93
+                </button>
94
+            </div>
95
+        </div>
96
+    </div>
97
+  `
98
+})
99
+export class KnexConfigComponent implements OnInit{
100
+
101
+  @Input("fbPlugin")
102
+  fbPlugin:string
103
+  conf: KnexConfig
104
+
105
+  pg:PgConfig = {
106
+    client: "pg",
107
+    version: "",
108
+    connection: {
109
+      database: "",
110
+      host: "",
111
+      password: "",
112
+      user: ""
113
+    }
114
+  }
115
+
116
+  mysql:MysqlConfig = {
117
+    client: 'mysql',
118
+    version: "",
119
+    connection: {
120
+      database: "",
121
+      host: "",
122
+      password: "",
123
+      user: ""
124
+    }
125
+  }
126
+
127
+  sqlite3:SqliteConfig = {
128
+    client: "sqlite3",
129
+    connection: {
130
+      filename: ""
131
+    }
132
+  }
133
+
134
+  mode:{[key in KnexDrivers]: boolean} = {
135
+    pg: false,
136
+    mysql: false,
137
+    sqlite3: true
138
+  }
139
+