浏览代码

hello

master
peter 6 年前
当前提交
7bc1d54934
共有 100 个文件被更改,包括 21352 次插入0 次删除
  1. 19
    0
      .gitignore
  2. 7
    0
      Dockerfile
  3. 11
    0
      FrontworkAdminConf.json
  4. 11
    0
      config/FrontworkAdminConf.json
  5. 6130
    0
      package-lock.json
  6. 67
    0
      package.json
  7. 117
    0
      src/backend/Admin.ts
  8. 22
    0
      src/backend/Installer.ts
  9. 2
    0
      src/backend/Launcher.ts
  10. 42
    0
      src/backend/RPCConfigLoader.ts
  11. 9
    0
      src/backend/Types.ts
  12. 68
    0
      src/backend/webpack.prod.js
  13. 60
    0
      src/frontend/.drone.yml
  14. 13
    0
      src/frontend/.editorconfig
  15. 48
    0
      src/frontend/.gitignore
  16. 6
    0
      src/frontend/.gitmodules
  17. 27
    0
      src/frontend/README.md
  18. 149
    0
      src/frontend/angular.json
  19. 12
    0
      src/frontend/browserslist
  20. 3
    0
      src/frontend/custom-webpack.config.js
  21. 32
    0
      src/frontend/e2e/protractor.conf.js
  22. 23
    0
      src/frontend/e2e/src/app.e2e-spec.ts
  23. 11
    0
      src/frontend/e2e/src/app.po.ts
  24. 13
    0
      src/frontend/e2e/tsconfig.json
  25. 32
    0
      src/frontend/karma.conf.js
  26. 12204
    0
      src/frontend/package-lock.json
  27. 70
    0
      src/frontend/package.json
  28. 9
    0
      src/frontend/proxy.conf.json
  29. 92
    0
      src/frontend/src/app/apiclient/apiclient-consumptions.component.ts
  30. 132
    0
      src/frontend/src/app/apiclient/apiclient-settings-form.component.ts
  31. 101
    0
      src/frontend/src/app/apiclient/apiclient-subscriptions.component.ts
  32. 32
    0
      src/frontend/src/app/apiclient/apiclient-widget.component.ts
  33. 61
    0
      src/frontend/src/app/apiclient/module.ts
  34. 37
    0
      src/frontend/src/app/app-routing.module.ts
  35. 5
    0
      src/frontend/src/app/app.component.html
  36. 0
    0
      src/frontend/src/app/app.component.scss
  37. 35
    0
      src/frontend/src/app/app.component.spec.ts
  38. 35
    0
      src/frontend/src/app/app.component.ts
  39. 75
    0
      src/frontend/src/app/app.module.ts
  40. 3
    0
      src/frontend/src/app/content-area/main.component.html
  41. 0
    0
      src/frontend/src/app/content-area/main.component.scss
  42. 25
    0
      src/frontend/src/app/content-area/main.component.spec.ts
  43. 17
    0
      src/frontend/src/app/content-area/main.component.ts
  44. 4
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.html
  45. 3
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.scss
  46. 25
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.spec.ts
  47. 153
    0
      src/frontend/src/app/dynamic-loader/dynamic-loader.component.ts
  48. 15
    0
      src/frontend/src/app/error-display/a-nested.component.ts
  49. 1
    0
      src/frontend/src/app/error-display/error-display.component.html
  50. 0
    0
      src/frontend/src/app/error-display/error-display.component.scss
  51. 25
    0
      src/frontend/src/app/error-display/error-display.component.spec.ts
  52. 15
    0
      src/frontend/src/app/error-display/error-display.component.ts
  53. 22
    0
      src/frontend/src/app/error-display/module.ts
  54. 17
    0
      src/frontend/src/app/header-bar/header-bar.component.html
  55. 0
    0
      src/frontend/src/app/header-bar/header-bar.component.scss
  56. 25
    0
      src/frontend/src/app/header-bar/header-bar.component.spec.ts
  57. 15
    0
      src/frontend/src/app/header-bar/header-bar.component.ts
  58. 1
    0
      src/frontend/src/app/home/home.component.html
  59. 0
    0
      src/frontend/src/app/home/home.component.scss
  60. 25
    0
      src/frontend/src/app/home/home.component.spec.ts
  61. 35
    0
      src/frontend/src/app/home/home.component.ts
  62. 15
    0
      src/frontend/src/app/htmlsupplier/a-nested.component.ts
  63. 24
    0
      src/frontend/src/app/htmlsupplier/component.ts
  64. 21
    0
      src/frontend/src/app/htmlsupplier/module.ts
  65. 25
    0
      src/frontend/src/app/knex-config/knex-config.component.spec.ts
  66. 205
    0
      src/frontend/src/app/knex-config/knex-config.component.ts
  67. 15
    0
      src/frontend/src/app/paymentmanager/a-nested.component.ts
  68. 24
    0
      src/frontend/src/app/paymentmanager/component.ts
  69. 22
    0
      src/frontend/src/app/paymentmanager/module.ts
  70. 47
    0
      src/frontend/src/app/pluginmanager/debug.component.ts
  71. 31
    0
      src/frontend/src/app/pluginmanager/module.ts
  72. 156
    0
      src/frontend/src/app/pluginmanager/plugins.component.ts
  73. 4
    0
      src/frontend/src/app/settings/settings.component.html
  74. 0
    0
      src/frontend/src/app/settings/settings.component.scss
  75. 25
    0
      src/frontend/src/app/settings/settings.component.spec.ts
  76. 39
    0
      src/frontend/src/app/settings/settings.component.ts
  77. 20
    0
      src/frontend/src/app/sidebar-entry-service.service.ts
  78. 42
    0
      src/frontend/src/app/sidebar/sidebar.component.html
  79. 0
    0
      src/frontend/src/app/sidebar/sidebar.component.scss
  80. 25
    0
      src/frontend/src/app/sidebar/sidebar.component.spec.ts
  81. 38
    0
      src/frontend/src/app/sidebar/sidebar.component.ts
  82. 17
    0
      src/frontend/src/app/subnav/subnav.component.html
  83. 0
    0
      src/frontend/src/app/subnav/subnav.component.scss
  84. 25
    0
      src/frontend/src/app/subnav/subnav.component.spec.ts
  85. 15
    0
      src/frontend/src/app/subnav/subnav.component.ts
  86. 1
    0
      src/frontend/src/assets/.gitkeep
  87. 19
    0
      src/frontend/src/assets/dev/FrontblockLib.js
  88. 二进制
      src/frontend/src/assets/logo_real.png
  89. 4
    0
      src/frontend/src/environments/environment.prod.ts
  90. 17
    0
      src/frontend/src/environments/environment.prodlike.ts
  91. 17
    0
      src/frontend/src/environments/environment.ts
  92. 二进制
      src/frontend/src/favicon.ico
  93. 15
    0
      src/frontend/src/index.html
  94. 12
    0
      src/frontend/src/main.ts
  95. 67
    0
      src/frontend/src/polyfills.ts
  96. 52
    0
      src/frontend/src/styles.scss
  97. 20
    0
      src/frontend/src/test.ts
  98. 19
    0
      src/frontend/tsconfig.app.json
  99. 26
    0
      src/frontend/tsconfig.json
  100. 0
    0
      src/frontend/tsconfig.prod.json

+ 19
- 0
.gitignore 查看文件

@@ -0,0 +1,19 @@
1
+dist
2
+.rpt2_cache
3
+node_modules
4
+lib
5
+kfs
6
+plugins
7
+static
8
+data
9
+conf
10
+
11
+*.d.ts
12
+*.js
13
+*.ts
14
+
15
+!src/**/*
16
+node_modules
17
+src/frontend/node_modules
18
+src/frontend/out-tsc
19
+src/frontend/dist

+ 7
- 0
Dockerfile 查看文件

@@ -0,0 +1,7 @@
1
+FROM node:12.9.0-alpine
2
+RUN apk add git
3
+
4
+RUN git clone https://gitea.frontblock.me/fb-dist/admin.git dist
5
+
6
+EXPOSE 8080 20000
7
+ENTRYPOINT ["node", "dist/FrontblockAdmin.js"]

+ 11
- 0
FrontworkAdminConf.json 查看文件

@@ -0,0 +1,11 @@
1
+{
2
+  "httpPort": 8080,
3
+  "eventBusConf": {},
4
+  "dbConf": {
5
+    "client": "sqlite3",
6
+    "connection": {
7
+      "filename": "/home/cake/frontwork/lib/data/frontworkAdmin.sqlite"
8
+    },
9
+    "useNullAsDefault": true
10
+  }
11
+}

+ 11
- 0
config/FrontworkAdminConf.json 查看文件

@@ -0,0 +1,11 @@
1
+{
2
+  "httpPort": 8080,
3
+  "eventBusConf": {},
4
+  "dbConf": {
5
+    "client": "sqlite3",
6
+    "connection": {
7
+      "filename": "/home/cake/frontwork/lib/data/frontworkAdmin.sqlite"
8
+    },
9
+    "useNullAsDefault": true
10
+  }
11
+}

+ 6130
- 0
package-lock.json
文件差异内容过多而无法显示
查看文件


+ 67
- 0
package.json 查看文件

@@ -0,0 +1,67 @@
1
+{
2
+  "name": "frontblock-admin",
3
+  "description": "Dynamic configurator for frontblock",
4
+  "version": "1.0.0",
5
+  "scripts": {
6
+    "tsc": "tsc",
7
+    "start": "npm run build; node dist/FrontblockAdmin.js",
8
+    "build": "npm run clean; npm run build-backend; npm run build-frontend",
9
+    "build-backend": "tsc; npm run webpack",
10
+    "build-frontend": "npm run build-dashboard; cp ./dist/FrontblockLib.js ./dist/static",
11
+    "build-dashboard": "git submodule init && git submodule update --merge; cd src/frontend; npm i && npm run build; mkdir ../../dist/static; cp -r dist/* ../../dist/static",
12
+    "clean": "rm -rf lib static plugins conf dist widget .rpt2_cache *.js *.ts src/frontend/dist data",
13
+    "update-frontblock": "npm remove frontblock frontblock-generic; npm install frontblock-generic@latest frontblock@latest",
14
+    "webpack": "webpack  --config src/backend/webpack.prod.js --progress --colors"
15
+  },
16
+  "repository": {
17
+    "type": "git",
18
+    "url": "http://gitea.frontblock.me/fb-vendor/admin"
19
+  },
20
+  "author": "frontblock.me",
21
+  "license": "ISC",
22
+  "dependencies": {
23
+    "bsert": "0.0.10",
24
+    "bsock": "^0.1.9",
25
+    "child-process-promise": "^2.2.1",
26
+    "debug": "^4.1.1",
27
+    "express": "^4.16.4",
28
+    "frontblock": "^0.15.2",
29
+    "frontblock-generic": "^0.34.8",
30
+    "git-cherrypicker": "0.0.3",
31
+    "git-describe": "^4.0.4",
32
+    "http": "0.0.0",
33
+    "knex": "^0.19.2",
34
+    "loadson": "^1.0.0",
35
+    "log4js": "^4.5.1",
36
+    "lowdb": "^1.0.0",
37
+    "node-fetch": "^2.6.0",
38
+    "path": "^0.12.7",
39
+    "rimraf": "^3.0.0",
40
+    "rpclibrary": "^1.3.0",
41
+    "simple-git": "^1.124.0",
42
+    "spawn-sync": "^2.0.0",
43
+    "sqlite3": "^4.1.0",
44
+    "trash": "^6.0.0",
45
+    "upgiter": "^1.0.1",
46
+    "uuid": "^3.3.2"
47
+  },
48
+  "devDependencies": {
49
+    "@types/express": "^4.17.0",
50
+    "@types/node": "^11.13.19",
51
+    "@types/semver": "^6.0.1",
52
+    "terser-webpack-plugin": "^1.4.1",
53
+    "ts-loader": "^5.3.3",
54
+    "typescript": "^3.5.3",
55
+    "webpack": "^4.39.2",
56
+    "webpack-cli": "^3.3.5"
57
+  },
58
+  "files": [
59
+    "lib/**/*"
60
+  ],
61
+  "main": "index.js",
62
+  "directories": {
63
+    "lib": "lib",
64
+    "test": "test"
65
+  },
66
+  "keywords": []
67
+}

+ 117
- 0
src/backend/Admin.ts 查看文件

@@ -0,0 +1,117 @@
1
+'use strict'
2
+
3
+import { getLogger } from 'frontblock-generic/Types';
4
+import { ConfigLoader } from 'loadson';
5
+import { promises as fs } from "fs"
6
+import { RPCServer } from 'rpclibrary/js/src/Backend'
7
+import { AdminConf } from './Types';
8
+import { RPCConfigLoader } from './RPCConfigLoader';
9
+
10
+import * as Path from 'path'
11
+
12
+import Knex = require('knex');
13
+import http = require('http');
14
+import express = require('express');
15
+
16
+const logger = getLogger("admin", 'debug') 
17
+
18
+export class FrontworkAdmin {
19
+    private express
20
+    private httpServer
21
+    private config: RPCConfigLoader<AdminConf>
22
+
23
+    constructor(){
24
+        this.initConfig()
25
+        this.startWebsocket()
26
+        this.startWebserver()
27
+    }
28
+
29
+    private initConfig(){
30
+        this.config = new RPCConfigLoader<AdminConf>({
31
+            name: "FrontworkAdminConf", 
32
+            getDefaultConfig: () => {
33
+                return {
34
+                    httpPort: 8080,
35
+                    eventBusConf: {},
36
+                    dbConf: {
37
+                        client: 'sqlite3',
38
+                        connection: {
39
+                            filename: Path.join(__dirname, "data/frontworkAdmin.sqlite")
40
+                        },
41
+                        useNullAsDefault: true
42
+                    }
43
+                }   
44
+            }
45
+        }, './config', console.log) 
46
+    }
47
+
48
+    private startWebsocket(){
49
+        console.log()
50
+        new RPCServer(20000, [
51
+            this.config
52
+        ])
53
+    }
54
+
55
+    private startWebserver(){
56
+        if(this.httpServer != null || this.express != null){
57
+            logger.warn("Webserver is already running")
58
+            return
59
+        }
60
+        
61
+        let port:number = this.config.getConfig().httpPort
62
+        this.express = express()
63
+        this.express.use('/', express.static('dist/static'))
64
+
65
+        /**
66
+         * get the compiled FrontendPlugins.js
67
+         */
68
+        this.express.get('/plugins/:id'+".js", async (request, response) => {
69
+            const pth = Path.resolve("plugins/"+request.params.id, "FrontendPlugin.js");
70
+            const file = await fs.readFile(pth)
71
+            const frontend = file.toString()
72
+
73
+            response.status(200)
74
+            response.set('Content-Type', 'application/javascript')
75
+            response.send(frontend)
76
+        })
77
+
78
+        /**
79
+         * serve the index.html from the static folder
80
+         */
81
+        this.express.get("/", (request, response) => {
82
+            response.status(200)
83
+            response.sendFile('index.html');
84
+        })
85
+
86
+        /**
87
+         * redirect all the other traffic to the single page app
88
+         */
89
+        this.express.get("*", (request, response) => {
90
+            response.status(301)
91
+            response.redirect('/')
92
+        })
93
+
94
+        this.httpServer = new http.Server(this.express)
95
+        this.httpServer.listen(port, () => {
96
+            logger.info('Admin panel listening for HTTP on *'+port)
97
+        })
98
+    }
99
+
100
+    private stopWebserver(){
101
+        if(this.httpServer == null || this.express == null){
102
+            logger.warn("Webserver is not running")
103
+            return
104
+        }
105
+        this.httpServer.close()
106
+        this.httpServer = null
107
+        this.express = null
108
+        logger.info("Webserver stopped")
109
+    }
110
+
111
+}
112
+
113
+process.on( 'SIGINT', function() {
114
+    logger.info("Shutting down from SIGINT (Ctrl-C)" );
115
+    // some other closing procedures go here
116
+    process.exit(0);
117
+})

+ 22
- 0
src/backend/Installer.ts 查看文件

@@ -0,0 +1,22 @@
1
+import { Plugin } from "frontblock-generic/Plugin"
2
+import { getLogger } from "frontblock-generic/Types"
3
+var exec = require('child-process-promise').exec;
4
+
5
+const logger = getLogger("installer", 'info') 
6
+
7
+export type NPMPkgName = string
8
+export type NPMVersion = string
9
+
10
+export const installAdmin = (plugins: Plugin[] = []) => {
11
+    
12
+    const npmPkgs:[NPMPkgName, NPMVersion][] = [['sqlite3', '4.1.0'], ['knex', '0.19.2']]
13
+    const deps = npmPkgs.map(tuple => tuple.join('@') ).join(" ")
14
+    logger.info("Installing plaform dependencies: "+deps)
15
+
16
+    exec("npm i " + deps).then(async process => {
17
+        logger.debug(process.stdout)    
18
+        const Admin = require("./Admin").FrontworkAdmin
19
+        const fbAdmin = new Admin(plugins)
20
+    })
21
+
22
+}

+ 2
- 0
src/backend/Launcher.ts 查看文件

@@ -0,0 +1,2 @@
1
+import { installAdmin } from "./Installer";
2
+installAdmin()

+ 42
- 0
src/backend/RPCConfigLoader.ts 查看文件

@@ -0,0 +1,42 @@
1
+import { ConfigLoader } from 'loadson'
2
+import { RPCExporter } from 'rpclibrary/js/src/Interfaces'
3
+
4
+export type ConfigLoaderIfc<ConfT> = {
5
+    Config : {
6
+        getConfig: () => ConfT
7
+        resetConfig: () => ConfT
8
+        setConfig: (conf:ConfT) => ConfT
9
+        setConfigKey: (key:string, value:any) => ConfT
10
+        deleteConfigKey: (key:string) => ConfT
11
+        getConfigKey: (key:string) => any
12
+    }
13
+}
14
+
15
+export class RPCConfigLoader<ConfT> 
16
+extends ConfigLoader<ConfT>
17
+implements RPCExporter<ConfigLoaderIfc<ConfT>, "Config">{
18
+
19
+    name = "Config" as "Config"
20
+
21
+    exportRPCs() {
22
+        return [{
23
+            name: "getConfig" as "getConfig",
24
+            call: () => { return this.getConfig() }
25
+        },{
26
+            name: "resetConfig" as "resetConfig",
27
+            call: () => { return this.resetConfig() }
28
+        },{
29
+            name: "setConfig" as "setConfig",
30
+            call: (conf:ConfT) => { return this.setConfig(conf) }
31
+        },{
32
+            name: "setConfigKey" as "setConfigKey",
33
+            call: (key:string, value:any) => { return this.setConfigKey(key, value) }
34
+        },{
35
+            name: "deleteConfigKey" as "deleteConfigKey",
36
+            call: (key:string) => { return this.deleteConfigKey(key) }
37
+        },{
38
+            name: "getConfigKey" as "getConfigKey",
39
+            call: (key:string) => { return this.getConfigKey(key) }
40
+        }]
41
+    }
42
+}

+ 9
- 0
src/backend/Types.ts 查看文件

@@ -0,0 +1,9 @@
1
+import Knex = require("knex")
2
+
3
+export declare type NotificationSeverity = 'Info' | 'Important' | 'Error';
4
+
5
+export type AdminConf = { 
6
+    httpPort: number,
7
+    dbConf:Knex.Config, 
8
+    eventBusConf: { [topic in string]: NotificationSeverity}
9
+}

+ 68
- 0
src/backend/webpack.prod.js 查看文件

@@ -0,0 +1,68 @@
1
+const path = require('path');
2
+
3
+module.exports = [{
4
+  mode: 'production',
5
+  target: "node",
6
+  node: {
7
+    global: true,
8
+    process: true,
9
+    __filename: false,
10
+    __dirname: false,
11
+    Buffer: true,
12
+  },
13
+  
14
+  resolve: {
15
+    // Add `.ts` and `.tsx` as a resolvable extension.
16
+
17
+    extensions: [".ts", ".tsx", ".js"]
18
+  },
19
+  module: {
20
+    rules: [
21
+      { test: /\.ts?$/, loader: "ts-loader" }
22
+    ]
23
+  },
24
+
25
+  externals: ['knex'],
26
+  optimization: {
27
+    minimize: false
28
+  },
29
+  entry: path.resolve(__dirname, 'Installer.ts'),
30
+  output: {
31
+      path: path.resolve(__dirname, '../../dist'),
32
+      filename: 'Installer.js',
33
+      libraryTarget: 'commonjs',
34
+  }
35
+},{
36
+  mode: 'production',
37
+  target: "node",
38
+  node: {
39
+    global: true,
40
+    process: true,
41
+    __filename: false,
42
+    __dirname: false,
43
+    Buffer: true,
44
+  },
45
+
46
+  
47
+  resolve: {
48
+    // Add `.ts` and `.tsx` as a resolvable extension.
49
+
50
+    extensions: [".ts", ".tsx", ".js"]
51
+  },
52
+  module: {
53
+    rules: [
54
+      { test: /\.ts?$/, loader: "ts-loader" }
55
+    ]
56
+  },
57
+
58
+  externals:["./Installer"],
59
+  optimization: {
60
+    minimize: false
61
+  },
62
+  entry: path.resolve(__dirname, 'Launcher.ts'),
63
+  output: {
64
+      path: path.resolve(__dirname, '../../dist'),
65
+      filename: 'FrontblockAdmin.js',
66
+      libraryTarget: 'commonjs',
67
+  }
68
+}]

+ 60
- 0
src/frontend/.drone.yml 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -0,0 +1,6 @@
1
+[submodule "src/app/paymentmanager"]
2
+	path = src/app/paymentmanager
3
+	url = ssh://git@gitea.frontblock.me:2222/fb-plugin/paymentmanager.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 查看文件

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

+ 149
- 0
src/frontend/angular.json 查看文件

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

+ 12
- 0
src/frontend/browserslist 查看文件

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

+ 3
- 0
src/frontend/custom-webpack.config.js 查看文件

@@ -0,0 +1,3 @@
1
+module.exports = {
2
+    externals: ['log4js']
3
+}

+ 32
- 0
src/frontend/e2e/protractor.conf.js 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

+ 12204
- 0
src/frontend/package-lock.json
文件差异内容过多而无法显示
查看文件


+ 70
- 0
src/frontend/package.json 查看文件

@@ -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
+    "get-submodules": "git submodule update --init && git submodule foreach git checkout master",
11
+    "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",
12
+    "deep-clean-plugins": "for module in $(git config --file .gitmodules --get-regexp path | awk '{ print $2 }'); do rm -rf $module/node_modules; done",
13
+    "test": "ng test",
14
+    "lint": "ng lint",
15
+    "e2e": "ng e2e",
16
+    "update-frontblock": "npm remove frontblock frontblock-generic; npm install frontblock-generic@latest frontblock@latest"
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": "^0.15.1",
36
+    "frontblock-generic": "^0.34.1",
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 查看文件

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

+ 92
- 0
src/frontend/src/app/apiclient/apiclient-consumptions.component.ts 查看文件

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

+ 132
- 0
src/frontend/src/app/apiclient/apiclient-settings-form.component.ts 查看文件

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

+ 101
- 0
src/frontend/src/app/apiclient/apiclient-subscriptions.component.ts 查看文件

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

+ 32
- 0
src/frontend/src/app/apiclient/apiclient-widget.component.ts 查看文件

@@ -0,0 +1,32 @@
1
+import { Component, OnInit } from '@angular/core';
2
+
3
+declare const fb
4
+
5
+@Component({
6
+    selector: '[apiclientconfig]',
7
+    template: `
8
+    consumers {{consumers}} subscribers {{subscribers}}
9
+  `
10
+})
11
+export class ApiclientWidgetComponent implements OnInit {
12
+
13
+    consumers = 0
14
+    subscribers = 0
15
+    
16
+    constructor() { }
17
+
18
+    async ngOnInit() {
19
+        await new Promise((resolve, reject)=>{
20
+            let awaitAdmin: { (): void; (...args: any[]): void; }
21
+            (awaitAdmin = () => {
22
+              if(fb.ApiClient != null){
23
+                resolve()
24
+              }
25
+              setTimeout(awaitAdmin,25)
26
+            })()
27
+        })
28
+
29
+        fb.ApiClient.getConsumers().then(cons => this.consumers = cons.length)
30
+        fb.ApiClient.getSubscriptions().then(subs => this.subscribers = subs.length)
31
+    }
32
+}

+ 61
- 0
src/frontend/src/app/apiclient/module.ts 查看文件

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

+ 37
- 0
src/frontend/src/app/app-routing.module.ts 查看文件

@@ -0,0 +1,37 @@
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
+import { SettingsComponent } from './settings/settings.component';
6
+
7
+const routes: Routes = [{
8
+    path: "apiclient",
9
+    loadChildren: () => import('./apiclient/module').then(mod => mod.ApiclientModule)
10
+  },{
11
+    path: "pluginmanager",
12
+    loadChildren: () => import('./pluginmanager/module').then(mod => mod.PluginmanagerModule)
13
+  },{
14
+    path: "settings",
15
+    component: SettingsComponent
16
+  },{
17
+    path: "home",
18
+    component: HomeComponent
19
+  },{
20
+    path: "",
21
+    pathMatch: "full",
22
+    redirectTo: "home",
23
+  },{
24
+    path: "**",
25
+    component: ErrorDisplayComponent
26
+  }];
27
+
28
+export function getRoutes(){
29
+  return routes
30
+}
31
+
32
+@NgModule({
33
+  imports: [RouterModule.forRoot(routes)],
34
+  exports: [RouterModule],
35
+})
36
+export class AppRoutingModule {
37
+}

+ 5
- 0
src/frontend/src/app/app.component.html 查看文件

@@ -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 查看文件


+ 35
- 0
src/frontend/src/app/app.component.spec.ts 查看文件

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

+ 35
- 0
src/frontend/src/app/app.component.ts 查看文件

@@ -0,0 +1,35 @@
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
+    let awaitSidebar: { (): void; (...args: any[]): void; }
24
+    (awaitSidebar = () => {
25
+      if(this.sidebar != null){
26
+        this.sidebarService.setSidebar(this.sidebar)
27
+        return
28
+      }
29
+      setTimeout(awaitSidebar,25)
30
+    })()
31
+
32
+    
33
+
34
+  }
35
+}

+ 75
- 0
src/frontend/src/app/app.module.ts 查看文件

@@ -0,0 +1,75 @@
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
+import { KnexConfigComponent } from './knex-config/knex-config.component';
21
+import { ApiclientFormComponent } from './apiclient/apiclient-settings-form.component';
22
+import { SettingsComponent } from './settings/settings.component';
23
+import { ApiclientModule } from './apiclient/module';
24
+
25
+
26
+export function createCompiler(fn: CompilerFactory): Compiler {
27
+  return fn.createCompiler();
28
+}
29
+
30
+const declarations = [ 
31
+  AppComponent,
32
+  
33
+  SidebarComponent,
34
+  MainComponent,
35
+  DynamicLoaderComponent,
36
+  HomeComponent,
37
+  HeaderBarComponent,
38
+  ErrorDisplayComponent,
39
+  SubnavComponent,
40
+  KnexConfigComponent,
41
+  SettingsComponent
42
+]
43
+
44
+@NgModule({
45
+  declarations: declarations,
46
+  imports: [
47
+    FormsModule,
48
+    BrowserModule,
49
+    BrowserAnimationsModule,
50
+    ClarityModule,
51
+    AppRoutingModule,
52
+    ApiclientModule
53
+  ],
54
+  entryComponents: [],
55
+  providers: [
56
+    {
57
+      provide: COMPILER_OPTIONS,
58
+      useValue: {},
59
+      multi: true
60
+    },
61
+    {
62
+      provide: CompilerFactory,
63
+      useClass: JitCompilerFactory,
64
+      deps: [COMPILER_OPTIONS]
65
+    },
66
+    {
67
+      provide: Compiler,
68
+      useFactory: createCompiler,
69
+      deps: [CompilerFactory]
70
+    }
71
+  ],
72
+  bootstrap: [AppComponent]
73
+})
74
+export class AppModule{
75
+}

+ 3
- 0
src/frontend/src/app/content-area/main.component.html 查看文件

@@ -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 查看文件


+ 25
- 0
src/frontend/src/app/content-area/main.component.spec.ts 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

+ 25
- 0
src/frontend/src/app/dynamic-loader/dynamic-loader.component.spec.ts 查看文件

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

+ 153
- 0
src/frontend/src/app/dynamic-loader/dynamic-loader.component.ts 查看文件

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

+ 15
- 0
src/frontend/src/app/error-display/a-nested.component.ts 查看文件

@@ -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 查看文件

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

+ 0
- 0
src/frontend/src/app/error-display/error-display.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/error-display/error-display.component.spec.ts 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

+ 17
- 0
src/frontend/src/app/header-bar/header-bar.component.html 查看文件

@@ -0,0 +1,17 @@
1
+<clr-header class="header header-7">
2
+    <div class="branding">
3
+        <a class="nav-link" [routerLink]="'home'">
4
+                <img src="assets/logo_real.png" class="clr-icon" />
5
+                <span class="title">&nbsp;&nbsp;Frontblock</span>
6
+        </a> 
7
+        
8
+    </div>
9
+    <div class="header-actions" style="z-index:2">
10
+        <a class="nav-link nav-icon" aria-label="bell" routerLinkActive="active" [routerLink]="'notifications'">
11
+            <clr-icon class="has-badge" shape="bell">  </clr-icon>
12
+        </a> 
13
+        <a class="nav-link nav-icon" routerLinkActive="active" [routerLink]="'settings'">
14
+            <clr-icon class="" shape="cog" ></clr-icon> 
15
+        </a>
16
+    </div>
17
+</clr-header>

+ 0
- 0
src/frontend/src/app/header-bar/header-bar.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/header-bar/header-bar.component.spec.ts 查看文件

@@ -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 查看文件

@@ -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 查看文件

@@ -0,0 +1 @@
1
+<ng-container #dynamicWidgets></ng-container>

+ 0
- 0
src/frontend/src/app/home/home.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/home/home.component.spec.ts 查看文件

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

+ 35
- 0
src/frontend/src/app/home/home.component.ts 查看文件

@@ -0,0 +1,35 @@
1
+import { Component, OnInit, ViewChild, ViewContainerRef, Injector, ComponentFactoryResolver, AfterViewInit } from '@angular/core';
2
+import { FrontendPlugin } from 'frontblock-generic/Plugin';
3
+import { ApiclientModule } from '../apiclient/module';
4
+
5
+@Component({
6
+  selector: 'app-home',
7
+  templateUrl: './home.component.html',
8
+  styleUrls: ['./home.component.scss']
9
+})
10
+export class HomeComponent implements AfterViewInit {
11
+
12
+  @ViewChild('dynamicWidgets', {read: ViewContainerRef, static: false})
13
+  settingsContainer: ViewContainerRef
14
+
15
+  constructor(
16
+    private componentFactoryResolver: ComponentFactoryResolver,
17
+    private injector: Injector
18
+  ) { }
19
+
20
+  ngAfterViewInit() {
21
+    this.injectModule(new ApiclientModule())
22
+
23
+  }
24
+
25
+  injectModule(module:FrontendPlugin<any>) {
26
+    if(!module.getSettingsComponent) return
27
+
28
+    //@ts-ignore
29
+    const factory = this.componentFactoryResolver.resolveComponentFactory(module.getWidget())
30
+    const component = factory.create(this.injector)
31
+    setTimeout(() => this.settingsContainer.insert(component.hostView), 1)
32
+  }
33
+
34
+
35
+}

+ 15
- 0
src/frontend/src/app/htmlsupplier/a-nested.component.ts 查看文件

@@ -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 查看文件

@@ -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 查看文件

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

+ 25
- 0
src/frontend/src/app/knex-config/knex-config.component.spec.ts 查看文件

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

+ 205
- 0
src/frontend/src/app/knex-config/knex-config.component.ts 查看文件

@@ -0,0 +1,205 @@
1
+import { Component, OnInit, Input } from '@angular/core';
2
+
3
+declare const fb : { Admin: { getConfig:()=>any , setConfigKey:(knex:"dbConf", conf:KnexConfig)=>any } }
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/db.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
+  conf: KnexConfig
102
+
103
+  pg:PgConfig = {
104
+    client: "pg",
105
+    version: "",
106
+    connection: {
107
+      database: "",
108
+      host: "",
109
+      password: "",
110
+      user: ""
111
+    }
112
+  }
113
+
114
+  mysql:MysqlConfig = {
115
+    client: 'mysql',
116
+    version: "",
117
+    connection: {
118
+      database: "",
119
+      host: "",
120
+      password: "",
121
+      user: ""
122
+    }
123
+  }
124
+
125
+  sqlite3:SqliteConfig = {
126
+    client: "sqlite3",
127
+    connection: {
128
+      filename: ""
129
+    }
130
+  }
131
+
132
+  mode:{[key in KnexDrivers]: boolean} = {
133
+    pg: false,
134
+    mysql: false,
135
+    sqlite3: true
136
+  }
137
+
138
+  async ngOnInit(){
139
+    await new Promise((resolve, reject)=>{
140
+        let awaitAdmin: { (): void; (...args: any[]): void; }
141
+        (awaitAdmin = () => {
142
+          if(fb.Admin != null){
143
+            resolve()
144
+          }
145
+          setTimeout(awaitAdmin,25)
146
+        })()
147
+    })
148
+
149
+    const c = await fb.Admin.getConfig()
150
+    this.conf = c.dbConf
151
+
152
+    Object.keys(this.mode).forEach(knexType => {
153
+      this.mode[knexType] = knexType === this.conf.client
154
+      if(this.mode[knexType]){
155
+        console.log(knexType, this.conf)
156
+        this[knexType] = this.conf
157
+      }
158
+    })
159
+
160
+    window['knex'] = this
161
+  }
162
+  
163
+  getSettingsComponentClassName(){
164
+    return KnexConfigComponent
165
+  }
166
+
167
+  save(){
168
+    const modeName = Object.entries(this.mode).find(([key, active]) => active)[0]
169
+    fb.Admin.setConfigKey('dbConf', this[modeName]).then(console.log)
170
+  }
171
+}
172
+
173
+type KnexDrivers = "pg" | "mysql" | "sqlite3"
174
+
175
+type BaseConfig<Driver extends KnexDrivers> = {
176
+  client: Driver
177
+}
178
+
179
+type PgConfig = BaseConfig<'pg'> & {
180
+  version: string
181
+  connection: {
182
+    host: string,
183
+    user: string,
184
+    password: string,
185
+    database: string
186
+  }
187
+}
188
+
189
+type MysqlConfig = BaseConfig<'mysql'> &  {
190
+  version: string
191
+  connection: {
192
+    host: string,
193
+    user: string,
194
+    password: string,
195
+    database: string
196
+  }
197
+}
198
+
199
+type SqliteConfig = BaseConfig<'sqlite3'> &  {
200
+  connection: {
201
+    filename: string
202
+  }
203
+}
204
+
205
+type KnexConfig = SqliteConfig | PgConfig | MysqlConfig

+ 15
- 0
src/frontend/src/app/paymentmanager/a-nested.component.ts 查看文件

@@ -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/paymentmanager/component.ts 查看文件

@@ -0,0 +1,24 @@
1
+import { Component, OnInit } from '@angular/core';
2
+declare const fb
3
+@Component({
4
+  selector: 'PAYMENTMANAGER', //!!!!
5
+  template: `
6
+  <div class="card">
7
+    <div class="card-block">
8
+      <div class="card-title">
9
+      PAYMENTMANAGER <br> ${Object.keys(fb.PaymentManager).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
+}

+ 22
- 0
src/frontend/src/app/paymentmanager/module.ts 查看文件

@@ -0,0 +1,22 @@
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: "paymentmanager", 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{
22
+}

+ 47
- 0
src/frontend/src/app/pluginmanager/debug.component.ts 查看文件

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

+ 31
- 0
src/frontend/src/app/pluginmanager/module.ts 查看文件

@@ -0,0 +1,31 @@
1
+import { NgModule } from '@angular/core';
2
+import { CommonModule } from '@angular/common';
3
+import { RouterModule } from '@angular/router';
4
+
5
+import { PluginmanagerDEBUG } from './debug.component';
6
+import { PluginsComponent } from './plugins.component';
7
+
8
+import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
9
+import { ClarityModule } from '@clr/angular';
10
+import { FormsModule } from '@angular/forms';
11
+
12
+
13
+@NgModule({
14
+  imports: [
15
+    FormsModule,
16
+    ClarityModule,
17
+
18
+    CommonModule, 
19
+    RouterModule.forChild([
20
+      {path: "debug", component: PluginmanagerDEBUG},
21
+      {path: "plugins", component: PluginsComponent}
22
+    ]),
23
+  ],
24
+  exports: [RouterModule],
25
+  declarations: [
26
+    PluginmanagerDEBUG,
27
+    PluginsComponent
28
+  ]
29
+})
30
+export class PluginmanagerModule{
31
+}

+ 156
- 0
src/frontend/src/app/pluginmanager/plugins.component.ts 查看文件

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

+ 4
- 0
src/frontend/src/app/settings/settings.component.html 查看文件

@@ -0,0 +1,4 @@
1
+<knex-config></knex-config>
2
+
3
+
4
+<ng-container #dynamicSettings></ng-container>

+ 0
- 0
src/frontend/src/app/settings/settings.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/settings/settings.component.spec.ts 查看文件

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

+ 39
- 0
src/frontend/src/app/settings/settings.component.ts 查看文件

@@ -0,0 +1,39 @@
1
+import { Component, ViewChild, ViewContainerRef, ComponentFactoryResolver, Injector, AfterViewInit } from '@angular/core';
2
+import { ApiclientModule } from '../apiclient/module'
3
+import { FrontendPlugin } from 'frontblock-generic/Plugin';
4
+import { DynamicLoaderComponent } from '../dynamic-loader/dynamic-loader.component';
5
+
6
+@Component({
7
+  selector: 'app-settings',
8
+  templateUrl: './settings.component.html',
9
+  styleUrls: ['./settings.component.scss']
10
+})
11
+export class SettingsComponent implements AfterViewInit {
12
+  async ngAfterViewInit(){
13
+    console.log(this.dynamicloader.settingsComponentFactories)
14
+    this.dynamicloader.settingsComponentFactories.forEach(componentFactory => {
15
+      this.settingsContainer.createComponent(componentFactory)
16
+    })
17
+
18
+    this.injectLocalModule(new ApiclientModule())
19
+
20
+  }
21
+
22
+  @ViewChild('dynamicSettings', {read: ViewContainerRef, static: false})
23
+  settingsContainer: ViewContainerRef
24
+
25
+  constructor(
26
+    private componentFactoryResolver: ComponentFactoryResolver,
27
+    private injector: Injector,
28
+    private dynamicloader:DynamicLoaderComponent
29
+  ) {
30
+  }
31
+
32
+  injectLocalModule(plugin:FrontendPlugin<any>) {
33
+    if(!plugin.getSettingsComponent) return
34
+
35
+    const factory = this.componentFactoryResolver.resolveComponentFactory(plugin.getSettingsComponent())
36
+    const component = factory.create(this.injector)
37
+    setTimeout(() => this.settingsContainer.insert(component.hostView), 1)
38
+  }
39
+}

+ 20
- 0
src/frontend/src/app/sidebar-entry-service.service.ts 查看文件

@@ -0,0 +1,20 @@
1
+import { Injectable } from '@angular/core';
2
+import { SidebarComponent } from './sidebar/sidebar.component';
3
+
4
+@Injectable({
5
+  providedIn: 'root'
6
+})
7
+export class SidebarEntryService {
8
+
9
+  private sidebar:SidebarComponent
10
+
11
+  constructor() { }
12
+
13
+  setSidebar(sidebar){
14
+    this.sidebar = sidebar
15
+  }
16
+
17
+  getSidebar():SidebarComponent{
18
+    return this.sidebar
19
+  }
20
+}

+ 42
- 0
src/frontend/src/app/sidebar/sidebar.component.html 查看文件

@@ -0,0 +1,42 @@
1
+<!--
2
+
3
+<nav class="sidenav">
4
+    <section class="sidenav-content">
5
+        <a *ngFor="let entry of entries" routerLinkActive="active" [routerLink]="entry.route" class="nav-link">
6
+            <clr-icon [attr.shape]="entry.icon" class="is-solid" clrVerticalNavIcon></clr-icon>{{entry.text}}
7
+        </a>
8
+
9
+        
10
+        <section class="nav-group" *ngFor="let e of multientires" >
11
+            <label> 
12
+                <clr-icon [attr.shape]="e.icon" class="is-solid"></clr-icon>
13
+                {{e.text}}
14
+            </label>
15
+            <ul class="nav-list">
16
+                <li *ngFor="let l of e.links"><a class="nav-link" routerLinkActive="active" [routerLink]="l.route" >{{l.text}}</a></li>
17
+            </ul>
18
+        </section>
19
+    </section>
20
+</nav>
21
+
22
+-->
23
+
24
+<clr-vertical-nav [clr-nav-level]="1" class="nav-trigger--bottom" style="height:  100%;" [clrVerticalNavCollapsible]="true" [(clrVerticalNavCollapsed)]="collapsed" >
25
+    <clr-vertical-nav-group *ngFor="let e of multientires" routerLinkActive="active">
26
+        <clr-icon [attr.shape]="e.icon" class="is-solid" clrVerticalNavIcon></clr-icon>
27
+        {{e.text}}
28
+        <clr-vertical-nav-group-children>
29
+            <a clrVerticalNavLink
30
+                *ngFor="let l of e.links"
31
+                [routerLink]="l.route"
32
+                routerLinkActive="active">
33
+                {{l.text}}
34
+            </a>
35
+        </clr-vertical-nav-group-children>
36
+    </clr-vertical-nav-group>
37
+    
38
+    <a *ngFor="let entry of entries" clrVerticalNavLink routerLinkActive="active" [routerLink]="entry.route">
39
+        <clr-icon [attr.shape]="entry.icon" class="is-solid" clrVerticalNavIcon></clr-icon>
40
+        {{entry.text}}
41
+    </a>
42
+</clr-vertical-nav>

+ 0
- 0
src/frontend/src/app/sidebar/sidebar.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/sidebar/sidebar.component.spec.ts 查看文件

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

+ 38
- 0
src/frontend/src/app/sidebar/sidebar.component.ts 查看文件

@@ -0,0 +1,38 @@
1
+import { Component, OnInit } from '@angular/core';
2
+import { SidebarEntries, SidebarEntry } from 'frontblock-generic/Plugin';
3
+import { ApiclientModule } from '../apiclient/module';
4
+
5
+@Component({
6
+  selector: 'sidebar',
7
+  templateUrl: './sidebar.component.html',
8
+  styleUrls: ['./sidebar.component.scss']
9
+})
10
+export class SidebarComponent implements OnInit {
11
+
12
+  collapsed = true
13
+
14
+  entries:SidebarEntry[] = []
15
+
16
+  multientires:SidebarEntries[] = [
17
+    <SidebarEntries> new ApiclientModule().getSidebarEntry(),
18
+    {
19
+    icon: "bundle",
20
+    text: "Update Manager",
21
+    parentRoute: "pluginmanager",
22
+    links: [{
23
+      route: "pluginmanager/debug",
24
+      text: "DEBUG"    
25
+    },{
26
+      route: "pluginmanager/admin",
27
+      text: "Dashboard"    
28
+    },{
29
+      route: "pluginmanager/plugins",
30
+      text: "Plugins"    
31
+    }]
32
+  },]
33
+
34
+  constructor() { }
35
+
36
+  ngOnInit() {
37
+  }
38
+}

+ 17
- 0
src/frontend/src/app/subnav/subnav.component.html 查看文件

@@ -0,0 +1,17 @@
1
+<!--
2
+<nav class="subnav">
3
+    <ul class="nav">
4
+        <li class="nav-item">
5
+            <a class="header-nav" routerLinkActive="active" [routerLink]="'home'">
6
+                <clr-icon class="is-solid" shape="home" size="24"></clr-icon> 
7
+            </a>
8
+            <a class="header-nav" routerLinkActive="active" [routerLink]="'settings'"> 
9
+                <clr-icon class="is-solid" shape="cog" size="24"></clr-icon> 
10
+            </a>
11
+            <a class="header-nav" routerLinkActive="active" [routerLink]="'notifications'">
12
+                <clr-icon class="is-solid has-badge" shape="bell" size="24"></clr-icon>
13
+            </a> 
14
+        </li>
15
+    </ul>
16
+</nav>
17
+-->

+ 0
- 0
src/frontend/src/app/subnav/subnav.component.scss 查看文件


+ 25
- 0
src/frontend/src/app/subnav/subnav.component.spec.ts 查看文件

@@ -0,0 +1,25 @@
1
+import { async, ComponentFixture, TestBed } from '@angular/core/testing';
2
+
3
+import { SubnavComponent } from './subnav.component';
4
+
5
+describe('SubnavComponent', () => {
6
+  let component: SubnavComponent;
7
+  let fixture: ComponentFixture<SubnavComponent>;
8
+
9
+  beforeEach(async(() => {
10
+    TestBed.configureTestingModule({
11
+      declarations: [ SubnavComponent ]
12
+    })
13
+    .compileComponents();
14
+  }));
15
+
16
+  beforeEach(() => {
17
+    fixture = TestBed.createComponent(SubnavComponent);
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/subnav/subnav.component.ts 查看文件

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

+ 1
- 0
src/frontend/src/assets/.gitkeep 查看文件

@@ -0,0 +1 @@
1
+./FrontblockLib.js

+ 19
- 0
src/frontend/src/assets/dev/FrontblockLib.js
文件差异内容过多而无法显示
查看文件


二进制
src/frontend/src/assets/logo_real.png 查看文件


+ 4
- 0
src/frontend/src/environments/environment.prod.ts 查看文件

@@ -0,0 +1,4 @@
1
+export const environment = {
2
+  production: true,
3
+  loadLocal: false //no effect in production mode
4
+};

+ 17
- 0
src/frontend/src/environments/environment.prodlike.ts 查看文件

@@ -0,0 +1,17 @@
1
+// This file can be replaced during build by using the `fileReplacements` array.
2
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3
+// The list of file replacements can be found in `angular.json`.
4
+
5
+export const environment = {
6
+  production: false,
7
+  loadLocal: false
8
+};
9
+
10
+/*
11
+ * For easier debugging in development mode, you can import the following file
12
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
13
+ *
14
+ * This import should be commented out in production mode because it will have a negative impact
15
+ * on performance if an error is thrown.
16
+ */
17
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.

+ 17
- 0
src/frontend/src/environments/environment.ts 查看文件

@@ -0,0 +1,17 @@
1
+// This file can be replaced during build by using the `fileReplacements` array.
2
+// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.
3
+// The list of file replacements can be found in `angular.json`.
4
+
5
+export const environment = {
6
+  production: false,
7
+  loadLocal: true
8
+};
9
+
10
+/*
11
+ * For easier debugging in development mode, you can import the following file
12
+ * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.
13
+ *
14
+ * This import should be commented out in production mode because it will have a negative impact
15
+ * on performance if an error is thrown.
16
+ */
17
+// import 'zone.js/dist/zone-error';  // Included with Angular CLI.

二进制
src/frontend/src/favicon.ico 查看文件


+ 15
- 0
src/frontend/src/index.html 查看文件

@@ -0,0 +1,15 @@
1
+<!doctype html>
2
+<html lang="en">
3
+<head>
4
+  <script src="FrontblockLib.js"></script>
5
+  <meta charset="utf-8">
6
+  <title>Frontblock Dashboard</title>
7
+  <base href="/">
8
+
9
+  <meta name="viewport" content="width=device-width, initial-scale=1">
10
+  <link rel="icon" type="image/x-icon" href="favicon.ico">
11
+</head>
12
+<body>
13
+  <app-root></app-root>
14
+</body>
15
+</html>

+ 12
- 0
src/frontend/src/main.ts 查看文件

@@ -0,0 +1,12 @@
1
+import { enableProdMode } from '@angular/core';
2
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
3
+
4
+import { AppModule } from './app/app.module';
5
+import { environment } from './environments/environment';
6
+
7
+if (environment.production) {
8
+  enableProdMode();
9
+}
10
+
11
+platformBrowserDynamic().bootstrapModule(AppModule)
12
+  .catch(err => console.error(err));

+ 67
- 0
src/frontend/src/polyfills.ts 查看文件

@@ -0,0 +1,67 @@
1
+/**
2
+ * This file includes polyfills needed by Angular and is loaded before the app.
3
+ * You can add your own extra polyfills to this file.
4
+ *
5
+ * This file is divided into 2 sections:
6
+ *   1. Browser polyfills. These are applied before loading ZoneJS and are sorted by browsers.
7
+ *   2. Application imports. Files imported after ZoneJS that should be loaded before your main
8
+ *      file.
9
+ *
10
+ * The current setup is for so-called "evergreen" browsers; the last versions of browsers that
11
+ * automatically update themselves. This includes Safari >= 10, Chrome >= 55 (including Opera),
12
+ * Edge >= 13 on the desktop, and iOS 10 and Chrome on mobile.
13
+ *
14
+ * Learn more in https://angular.io/guide/browser-support
15
+ */
16
+
17
+/***************************************************************************************************
18
+ * BROWSER POLYFILLS
19
+ */
20
+
21
+/** IE10 and IE11 requires the following for NgClass support on SVG elements */
22
+// import 'classlist.js';  // Run `npm install --save classlist.js`.
23
+
24
+/**
25
+ * Web Animations `@angular/platform-browser/animations`
26
+ * Only required if AnimationBuilder is used within the application and using IE/Edge or Safari.
27
+ * Standard animation support in Angular DOES NOT require any polyfills (as of Angular 6.0).
28
+ */
29
+// import 'web-animations-js';  // Run `npm install --save web-animations-js`.
30
+
31
+/**
32
+ * By default, zone.js will patch all possible macroTask and DomEvents
33
+ * user can disable parts of macroTask/DomEvents patch by setting following flags
34
+ * because those flags need to be set before `zone.js` being loaded, and webpack
35
+ * will put import in the top of bundle, so user need to create a separate file
36
+ * in this directory (for example: zone-flags.ts), and put the following flags
37
+ * into that file, and then add the following code before importing zone.js.
38
+ * import './zone-flags.ts';
39
+ *
40
+ * The flags allowed in zone-flags.ts are listed here.
41
+ *
42
+ * The following flags will work for all browsers.
43
+ *
44
+ * (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
45
+ * (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
46
+ * (window as any).__zone_symbol__UNPATCHED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
47
+ *
48
+ *  in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
49
+ *  with the following flag, it will bypass `zone.js` patch for IE/Edge
50
+ *
51
+ *  (window as any).__Zone_enable_cross_context_check = true;
52
+ *
53
+ */
54
+
55
+/***************************************************************************************************
56
+ * Zone JS is required by default for Angular itself.
57
+ */
58
+import 'zone.js/dist/zone';  // Included with Angular CLI.
59
+
60
+
61
+/***************************************************************************************************
62
+ * APPLICATION IMPORTS
63
+ */
64
+// Add global to window, assigning the value of window itself.
65
+(window as any).global = window;
66
+global.Buffer = global.Buffer || require('buffer').Buffer;
67
+global.process = global.process || require('process')

+ 52
- 0
src/frontend/src/styles.scss 查看文件

@@ -0,0 +1,52 @@
1
+/* You can add global styles to this file, and also import other style files */
2
+@import "../node_modules/@clr/ui/src/utils/components.clarity";
3
+
4
+.heightmax{
5
+    height: 100%
6
+}
7
+.break-word {
8
+    word-wrap: break-word;
9
+  }
10
+.monospace {
11
+    font-family: monospace; 
12
+    font-size: 90%
13
+}
14
+*::-webkit-scrollbar-track
15
+{
16
+    box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
17
+	-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,0.3);
18
+	background-color: rgba(0,0,0,0);
19
+	border-radius: 10px;
20
+}
21
+
22
+*::-webkit-scrollbar
23
+{
24
+	width: 7px;
25
+	background-color: rgba(0,0,0,0);
26
+}
27
+
28
+*::-webkit-scrollbar-thumb
29
+{
30
+    border-radius: 7px;
31
+    background-color: #0079B8;
32
+}
33
+
34
+.clr-all-12{
35
+    @extend .clr-col-12, .clr-col-sm-12, .clr-col-md-12, .clr-col-lg-12, .clr-col-xl-12
36
+}
37
+
38
+.clr-all-4{
39
+    @extend .clr-col-4, .clr-col-sm-4, .clr-col-md-4, .clr-col-lg-4, .clr-col-xl-4
40
+}
41
+
42
+.clr-all-6{
43
+    @extend .clr-col-6, .clr-col-sm-6, .clr-col-md-6, .clr-col-lg-6, .clr-col-xl-6
44
+}
45
+
46
+.clr-all-3{
47
+    @extend .clr-col-3, .clr-col-sm-3, .clr-col-md-3, .clr-col-lg-3, .clr-col-xl-3
48
+}
49
+
50
+.clr-all-2{
51
+    @extend .clr-col-2, .clr-col-sm-2, .clr-col-md-2, .clr-col-lg-2, .clr-col-xl-2
52
+}

+ 20
- 0
src/frontend/src/test.ts 查看文件

@@ -0,0 +1,20 @@
1
+// This file is required by karma.conf.js and loads recursively all the .spec and framework files
2
+
3
+import 'zone.js/dist/zone-testing';
4
+import { getTestBed } from '@angular/core/testing';
5
+import {
6
+  BrowserDynamicTestingModule,
7
+  platformBrowserDynamicTesting
8
+} from '@angular/platform-browser-dynamic/testing';
9
+
10
+declare const require: any;
11
+
12
+// First, initialize the Angular testing environment.
13
+getTestBed().initTestEnvironment(
14
+  BrowserDynamicTestingModule,
15
+  platformBrowserDynamicTesting()
16
+);
17
+// Then we find all the tests.
18
+const context = require.context('./', true, /\.spec\.ts$/);
19
+// And load the modules.
20
+context.keys().map(context);

+ 19
- 0
src/frontend/tsconfig.app.json 查看文件

@@ -0,0 +1,19 @@
1
+{
2
+  "extends": "./tsconfig.json",
3
+  "compilerOptions": {
4
+    "outDir": "./out-tsc/app",
5
+    "types": ["node"]
6
+  },
7
+  "include": [
8
+    "src/**/*.ts"
9
+  ],
10
+  "exclude": [
11
+    "node_modules",
12
+    "src/test.ts",
13
+    "src/**/*.spec.ts",
14
+    "src/app/**/*/node_modules/**/*",
15
+    "src/app/**/*/dist/**/*",
16
+    "src/app/**/*/backend/**/*",
17
+    "src/app/apiclient/*.ts"
18
+  ]
19
+}

+ 26
- 0
src/frontend/tsconfig.json 查看文件

@@ -0,0 +1,26 @@
1
+{
2
+  "compileOnSave": false,
3
+  "compilerOptions": {
4
+    "baseUrl": "./",
5
+    "outDir": "./out-tsc",
6
+    "sourceMap": true,
7
+    "declaration": false,
8
+    "downlevelIteration": true,
9
+    "experimentalDecorators": true,
10
+    "module": "commonjs",
11
+    "moduleResolution": "node",
12
+    "importHelpers": true,
13
+    "target": "es2015",
14
+    "typeRoots": [
15
+      "node_modules/@types"
16
+    ],
17
+    "lib": [
18
+      "es2018",
19
+      "dom"
20
+    ]
21
+  },
22
+  "angularCompilerOptions": {
23
+    "fullTemplateTypeCheck": true,
24
+    "strictInjectionParameters": true
25
+  }
26
+}

+ 0
- 0
src/frontend/tsconfig.prod.json 查看文件


部分文件因为文件数量过多而无法显示

正在加载...
取消
保存