This commit is contained in:
peter
2019-08-15 23:07:07 +02:00
parent 434cce25f2
commit 193a4e8c12
23 changed files with 1197 additions and 171 deletions
+2 -1
View File
@@ -11,7 +11,8 @@
"build-dashboard": "cd src/frontend/dashboard; npm run build; cp -r dist/dashboard/* ../../../static", "build-dashboard": "cd src/frontend/dashboard; npm run build; cp -r dist/dashboard/* ../../../static",
"build-widget": "rollup -c src/frontend/widget/rollup.config.js; mkdir -p plugins/PluginManager; cp FrontendPlugin.js plugins/PluginManager", "build-widget": "rollup -c src/frontend/widget/rollup.config.js; mkdir -p plugins/PluginManager; cp FrontendPlugin.js plugins/PluginManager",
"clean": "rm -rf lib static plugins data conf dist widget .rpt2_cache *.js *.ts src/frontend/dashboard/dist", "clean": "rm -rf lib static plugins data conf dist widget .rpt2_cache *.js *.ts src/frontend/dashboard/dist",
"update-frontblock": "rm -rf node_modules/frontblock*; npm install" "update-frontblock": "rm -rf node_modules/frontblock*; npm install",
"webpack": "webpack --entry ./lib/FrontblockLib.js --output ./FrontblockLib.min.js"
}, },
"repository": { "repository": {
"type": "git", "type": "git",
+16
View File
@@ -2588,6 +2588,17 @@
"bsert": "~0.0.10" "bsert": "~0.0.10"
} }
}, },
"btc-hdkey": {
"version": "0.0.17",
"resolved": "https://registry.npmjs.org/btc-hdkey/-/btc-hdkey-0.0.17.tgz",
"integrity": "sha512-1hzd8DeK/cjrBY6nJ+LenBTGL5rQ+dFBaDEoDSmQ/7zvV3EoV9rsa/aYLLke0Q5XPfbxC1lV/Ro5KpNULA7EYQ==",
"requires": {
"bip39": "^3.0.2",
"bitcoinjs-lib": "^5.1.3",
"bsert": "0.0.10",
"hdkey": "^1.1.1"
}
},
"buffer": { "buffer": {
"version": "4.9.1", "version": "4.9.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz", "resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz",
@@ -2973,6 +2984,11 @@
} }
} }
}, },
"coinselect": {
"version": "3.1.11",
"resolved": "https://registry.npmjs.org/coinselect/-/coinselect-3.1.11.tgz",
"integrity": "sha1-4fBjvRpYgvZzXuBRm52LWsSpMJk="
},
"coinstring": { "coinstring": {
"version": "2.3.0", "version": "2.3.0",
"resolved": "https://registry.npmjs.org/coinstring/-/coinstring-2.3.0.tgz", "resolved": "https://registry.npmjs.org/coinstring/-/coinstring-2.3.0.tgz",
+4
View File
@@ -24,12 +24,16 @@
"@clr/icons": "^2.1.1", "@clr/icons": "^2.1.1",
"@clr/ui": "^2.1.1", "@clr/ui": "^2.1.1",
"@webcomponents/custom-elements": "^1.0.0", "@webcomponents/custom-elements": "^1.0.0",
"btc-hdkey": "0.0.17",
"coinselect": "^3.1.11",
"frontblock": "latest", "frontblock": "latest",
"frontblock-generic": "^0.22.0", "frontblock-generic": "^0.22.0",
"key-file-storage": "^2.2.4", "key-file-storage": "^2.2.4",
"node-fetch": "^2.6.0",
"rxjs": "~6.5.2", "rxjs": "~6.5.2",
"systemjs": "^0.21.3", "systemjs": "^0.21.3",
"tslib": "^1.9.0", "tslib": "^1.9.0",
"uuid": "^3.3.2",
"zone.js": "~0.10.1" "zone.js": "~0.10.1"
}, },
"devDependencies": { "devDependencies": {
@@ -1,40 +1,16 @@
import { Component, OnInit, isDevMode } from '@angular/core'; import { Component, OnInit, isDevMode } from '@angular/core';
import { SubscriptionResponse, parseResponse, SuccessResponse } from 'frontblock-generic/Types.js'; import { SubscriptionResponse, parseResponse, SuccessResponse, Coin } from 'frontblock-generic/Types.js';
import { default as uuid } from 'uuid/v4'
declare const fb
const fb = !isDevMode() ? window["fb"] : {
ApiClient: {
setConfig: async () => {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise A win!');
}, 500)
})
},
getConsumptions: async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new SubscriptionResponse("654", "321"), new SubscriptionResponse("456", "123")]);
}, 500)
})
},
getSubscriptions: async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new SubscriptionResponse("321"), new SubscriptionResponse("123")]);
}, 500)
})
},
unsubscribe: (uid) => new SuccessResponse(),
quit: (uid) => new SuccessResponse()
}
}
@Component({ @Component({
selector: 'consumptions', selector: 'consumers',
template: ` template: `
<div class="clr-row"> <div class="clr-row">
<div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12"> <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-auto clr-col-xl-auto">
<div class="card-header"> <div class="card-header">
Consumptions Consumers
</div> </div>
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity"> <clr-alert *ngFor="let entry of alerts" [clrAlertType]="entry.severity">
<clr-alert-item> <clr-alert-item>
@@ -47,16 +23,18 @@ const fb = !isDevMode() ? window["fb"] : {
<div *ngIf="loading" class="card-block"> <div *ngIf="loading" class="card-block">
<span class="spinner spinner-inline"></span> <span class="spinner spinner-inline"></span>
</div> </div>
<div *ngIf="entries.length !== 0 && !loading" class="card-block"> <div *ngIf="entries.length !== 0 && !loading" class="card-block limit-height">
<div class="card-text"> <div class="card-text limit-height">
<clr-datagrid> <clr-datagrid>
<clr-dg-column>UUID</clr-dg-column> <clr-dg-column>UUID</clr-dg-column>
<clr-dg-column>Subscription UUID</clr-dg-column>
<clr-dg-column>Expiry</clr-dg-column> <clr-dg-column>Expiry</clr-dg-column>
<clr-dg-column>Creation</clr-dg-column> <clr-dg-column>Creation</clr-dg-column>
<clr-dg-row *ngFor="let line of entries"> <clr-dg-row *clrDgItems="let line of entries">
<clr-dg-cell>{{line.uid}}</clr-dg-cell> <clr-dg-cell>{{line.uid}}</clr-dg-cell>
<clr-dg-cell>{{line.expiry}}</clr-dg-cell> <clr-dg-cell>{{line.message}}</clr-dg-cell>
<clr-dg-cell>{{line.expiry | date}}</clr-dg-cell>
<clr-dg-cell>{{line.created | date}}</clr-dg-cell> <clr-dg-cell>{{line.created | date}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded> <clr-dg-row-detail *clrIfExpanded>
@@ -64,7 +42,11 @@ const fb = !isDevMode() ? window["fb"] : {
</clr-dg-row-detail> </clr-dg-row-detail>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
{{entries.length}} entries <clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}} users
</clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div> </div>
@@ -1,35 +0,0 @@
import { Component, OnInit } from '@angular/core';
import { SidebarEntry } from 'frontblock-generic/Plugin';
import { environment } from "../../environments/environment"
@Component({
selector: 'apiclient', //!!!!
template: `
<div class="clr-row">
<div class="clr-col-lg-auto">
<settings></settings>
</div>
<div class="clr-col-12 clr-col-lg-12 clr-col-xl-12 clr-col-sm-12">
<consumptions></consumptions>
</div>
</div>
<div class="clr-row">
<div class="clr-col">
<subscriptions></subscriptions>
</div>
</div>
`
})
export class ApiclientDevComponent implements OnInit {
constructor() { }
ngOnInit() { }
}
export const sidebarEntry:SidebarEntry = {
icon: "wrench",
route: "dev/apiclient",
text: "DEV ApiClient",
}
@@ -2,17 +2,7 @@ import { Component, OnInit } from '@angular/core';
import { FrontblockApiConf } from 'frontblock'; import { FrontblockApiConf } from 'frontblock';
import { isDevMode } from '@angular/core'; import { isDevMode } from '@angular/core';
const fb = !isDevMode ? window["fb"] : { declare const fb
ApiClient: {
setConfig: async () => {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise A win!');
}, 500)
})
}
}
}
@Component({ @Component({
selector: 'settings', selector: 'settings',
@@ -1,33 +1,7 @@
import { Component, OnInit, isDevMode } from '@angular/core'; import { Component, OnInit, isDevMode } from '@angular/core';
import { SubscriptionResponse, SuccessResponse, parseResponse } from 'frontblock-generic/Types.js'; import { SubscriptionResponse, SuccessResponse, parseResponse, Coin } from 'frontblock-generic/Types.js';
import { default as uuid } from 'uuid/v4'
const fb = !isDevMode() ? window["fb"] : { declare const fb
ApiClient: {
setConfig: async () => {
await new Promise((resolve, reject) => {
setTimeout(() => {
resolve('Promise A win!');
}, 500)
})
},
getConsumptions: async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new SubscriptionResponse("654", "321"), new SubscriptionResponse("456", "123")]);
}, 500)
})
},
getSubscriptions: async () => {
return await new Promise((resolve, reject) => {
setTimeout(() => {
resolve([new SubscriptionResponse("321"), new SubscriptionResponse("123")]);
}, 500)
})
},
unsubscribe: (uid) => new SuccessResponse(),
quit: (uid) => new SuccessResponse()
}
}
@Component({ @Component({
selector: 'subscriptions', selector: 'subscriptions',
@@ -52,20 +26,34 @@ const fb = !isDevMode() ? window["fb"] : {
<div class="card-text"> <div class="card-text">
<clr-datagrid> <clr-datagrid>
<clr-dg-column>UUID</clr-dg-column> <clr-dg-column>UUID</clr-dg-column>
<!--
<clr-dg-column>Currency</clr-dg-column>
<clr-dg-column>Address</clr-dg-column>
<clr-dg-column>Memo</clr-dg-column>
<clr-dg-column>Expiry</clr-dg-column> <clr-dg-column>Expiry</clr-dg-column>
<clr-dg-column>Creation</clr-dg-column> <clr-dg-column>Creation</clr-dg-column>
-->
<clr-dg-row *ngFor="let line of entries"> <clr-dg-row *clrDgItems="let line of entries">
<clr-dg-cell>{{line.uid}}</clr-dg-cell> <clr-dg-cell>{{line.uid}}</clr-dg-cell>
<clr-dg-cell>{{line.expiry}}</clr-dg-cell> <!--
<clr-dg-cell>{{line.currency}}</clr-dg-cell>
<clr-dg-cell>{{line.address}}</clr-dg-cell>
<clr-dg-cell>{{line.memo}}</clr-dg-cell>
<clr-dg-cell>{{line.expiry | date}}</clr-dg-cell>
<clr-dg-cell>{{line.created | date}}</clr-dg-cell> <clr-dg-cell>{{line.created | date}}</clr-dg-cell>
-->
<clr-dg-row-detail *clrIfExpanded> <clr-dg-row-detail *clrIfExpanded>
<button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Unsubscribe</button> <button class="btn btn-icon btn-danger-outline" (click)="quit(line.uid)"><clr-icon shape="times"></clr-icon> Unsubscribe</button>
</clr-dg-row-detail> </clr-dg-row-detail>
</clr-dg-row> </clr-dg-row>
<clr-dg-footer> <clr-dg-footer>
{{entries.length}} entries <clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">Users per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}} users
</clr-dg-pagination>
</clr-dg-footer> </clr-dg-footer>
</clr-datagrid> </clr-datagrid>
</div> </div>
@@ -81,7 +69,7 @@ const fb = !isDevMode() ? window["fb"] : {
export class ApiclientSubscriptionComponent implements OnInit { export class ApiclientSubscriptionComponent implements OnInit {
loading: boolean = false loading: boolean = false
actionName: string = "load" actionName: string = "load"
entries: SubscriptionResponse[] = [] entries: (SubscriptionResponse )[] = []
alerts: { alerts: {
severity: "danger" | "warning" | "success" severity: "danger" | "warning" | "success"
message: string message: string
@@ -2,7 +2,6 @@ import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router'; import { RouterModule } from '@angular/router';
import { ApiclientDevComponent as ApiclientComponent } from './apiclient-dev.component';
import { ApiclientFormComponent } from './apiclient-settings-form.component'; import { ApiclientFormComponent } from './apiclient-settings-form.component';
import { ApiclientConsumptionComponent } from './apiclient-consumptions.component'; import { ApiclientConsumptionComponent } from './apiclient-consumptions.component';
import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component'; import { ApiclientSubscriptionComponent } from './apiclient-subscriptions.component';
@@ -21,12 +20,11 @@ import { FormsModule } from '@angular/forms';
RouterModule.forChild([ RouterModule.forChild([
{path: "settings", component: ApiclientFormComponent}, {path: "settings", component: ApiclientFormComponent},
{path: "subscriptions", component: ApiclientSubscriptionComponent}, {path: "subscriptions", component: ApiclientSubscriptionComponent},
{path: "consumptions", component: ApiclientConsumptionComponent}, {path: "consumers", component: ApiclientConsumptionComponent},
]), ]),
], ],
exports: [RouterModule], exports: [RouterModule],
declarations: [ declarations: [
ApiclientComponent,
ApiclientFormComponent, ApiclientFormComponent,
ApiclientConsumptionComponent, ApiclientConsumptionComponent,
ApiclientSubscriptionComponent ApiclientSubscriptionComponent
@@ -42,8 +40,8 @@ export class PluginModule implements FrontendPlugin{
route: "settings", route: "settings",
text: "Settings" text: "Settings"
},{ },{
route: "consumptions", route: "consumers",
text: "Consumptions" text: "Consumers"
},{ },{
route: "subscriptions", route: "subscriptions",
text: "Subscriptions" text: "Subscriptions"
@@ -1,4 +1,4 @@
import { Component, OnInit } from '@angular/core'; import { Component, OnInit, isDevMode } from '@angular/core';
declare const fb declare const fb
@Component({ @Component({
selector: 'plugins', //!!!! selector: 'plugins', //!!!!
@@ -15,32 +15,61 @@ declare const fb
</clr-modal> </clr-modal>
<div class="clr-row"> <div class="clr-row">
<div class="clr-col"> <div class="card clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
<div class="card">
<div class="card-header"> <div class="card-header">
Plugins Plugins
</div> </div>
<div class="card-block"> <clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
<clr-alert-item>
<span class="alert-text">
{{line.message}}
</span>
</clr-alert-item>
</clr-alert>
<div *ngIf="loading" class="card-block">
<span class="spinner spinner-inline"></span>
</div>
<div *ngIf="plugins.length !== 0 && !loading" class="card-block">
<div class="card-text"> <div class="card-text">
<clr-accordion> <clr-datagrid>
<clr-accordion-panel *ngFor="let entry of plugins"> <clr-dg-column>Name</clr-dg-column>
<clr-accordion-title>{{entry.name}}</clr-accordion-title> <clr-dg-column>Available</clr-dg-column>
<clr-accordion-content *clrIfExpanded> <clr-dg-column>Installed</clr-dg-column>
<button *ngIf="entry.status === 'Stopped'" class="btn btn-icon btn-success-outline" (click)="start(entry.name)"><clr-icon shape="play"></clr-icon></button> <clr-dg-column>Status</clr-dg-column>
<button *ngIf="entry.status === 'Running'" class="btn btn-icon btn-warning-outline" (click)="stop(entry.name)"><clr-icon shape="stop"></clr-icon></button>
<button *ngIf="entry.status === 'Stopped'" class="btn btn-icon btn-danger-outline" (click)="delete(entry.name)"><clr-icon shape="trash"></clr-icon></button> <clr-dg-row *clrDgItems="let line of plugins">
<button *ngIf="entry.status === 'Available'" class="btn btn-icon btn-info-outline" (click)="showConfirmation(entry.name)"><clr-icon shape="download"></clr-icon></button> <clr-dg-cell>{{line.name}}</clr-dg-cell>
<button *ngIf="entry.available !== entry.installed && entry.status === 'Stopped'" class="btn btn-icon btn-outline " (click)="showConfirmation(entry.name)"> <clr-dg-cell>
<span *ngIf="line.installed != line.available" class="label label-light-blue">{{line.available}}</span>
<span *ngIf="line.installed == line.available">{{line.available}}</span>
</clr-dg-cell>
<clr-dg-cell>{{line.installed}}</clr-dg-cell>
<clr-dg-cell>{{line.status}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded>
<button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-success-outline" (click)="start(line.name)"><clr-icon shape="play"></clr-icon></button>
<button *ngIf="line.status === 'Running'" class="btn btn-icon btn-warning-outline" (click)="stop(line.name)"><clr-icon shape="pause"></clr-icon></button>
<button *ngIf="line.status === 'Stopped'" class="btn btn-icon btn-danger-outline" (click)="delete(line.name)"><clr-icon shape="trash"></clr-icon></button>
<button *ngIf="line.status === 'Available'" class="btn btn-icon btn-info-outline" (click)="showConfirmation(line.name)"><clr-icon shape="download"></clr-icon></button>
<button *ngIf="line.available !== line.installed && line.status === 'Stopped'" class="btn btn-icon btn-outline " (click)="showConfirmation(line.name)">
<clr-icon shape="sync"></clr-icon> <clr-icon shape="sync"></clr-icon>
</button> </button>
</clr-accordion-content> </clr-dg-row-detail>
</clr-accordion-panel> </clr-dg-row>
</clr-accordion> <clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="10">
<clr-dg-page-size [clrPageSizeOptions]="[10,20,50,100]">per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div> </div>
</div> </div>
<div class="card-footer"> <div class="card-footer">
<!-- <button class="btn btn-success" onclick="setup()">HELLO</button> --> <button *ngIf="!reloading" class="btn btn-sm btn-link" (click)="reload()">Check</button>
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -56,14 +85,20 @@ export class PluginsComponent implements OnInit {
name: "test II: Return of test", name: "test II: Return of test",
available: "0.0.1", available: "0.0.1",
installed: "0.0.1", installed: "0.0.1",
status: "Available" status: "Stopped"
}, { }, {
name: "test 3", name: "test 3",
available: "0.0.2", available: "0.0.2",
installed: "0.0.1", installed: "",
status: "Available"
}, {
name: "test IV: minor bump",
available: "0.9.27",
installed: "0.0.11",
status: "Stopped" status: "Stopped"
},] },]
reloading = false
updatepending = false updatepending = false
updateCandidate = "" updateCandidate = ""
@@ -78,7 +113,9 @@ export class PluginsComponent implements OnInit {
} }
delete(name) { delete(name) {
this.plugins.find((el) => el.name === name).status = "Available" let plugin = this.plugins.find((el) => el.name === name)
plugin.status = "Available"
plugin.installed = ""
} }
showConfirmation(name) { showConfirmation(name) {
@@ -98,6 +135,21 @@ export class PluginsComponent implements OnInit {
this.updatepending = !this.updatepending this.updatepending = !this.updatepending
} }
async reload(){
this.reloading = true
if(isDevMode()){
await new Promise((resolve, reject) => {
setTimeout(resolve, 1000)
})
this.plugins = this.plugins.map(plugin => {
const parts = plugin.available.split('.')
plugin.available = parts[0]+"."+parts[1]+"."+(parseInt(parts[2])+1)
return plugin
})
this.reloading = false
}
}
ngOnInit() { } ngOnInit() { }
} }
@@ -0,0 +1,115 @@
import { Component, OnInit, isDevMode } from '@angular/core';
import { PluginsComponent } from './btc.component';
declare const fb
@Component({
selector: 'address-viewer', //!!!!
template: `
<div class="card">
<div class="card-header">
Addresses
</div>
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
<clr-alert-item>
<span class="alert-text">
{{line.message}}
</span>
</clr-alert-item>
</clr-alert>
<clr-datagrid class="datagrid-compact">
<clr-dg-column>Address</clr-dg-column>
<clr-dg-column>Balance</clr-dg-column>
<clr-dg-column>Pending</clr-dg-column>
<clr-dg-column>Estimate total</clr-dg-column>
<clr-dg-row *clrDgItems="let line of regularAddresses">
<clr-dg-cell style="word-break: break-all">{{line.address}}</clr-dg-cell>
<clr-dg-cell>{{line.confirmed.balance}}</clr-dg-cell>
<clr-dg-cell>{{line.unconfirmed.balance}}</clr-dg-cell>
<clr-dg-cell>{{line.total.balance}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded >
<div class="clr-row">
<div class="clr-col-3">
<button class="btn btn-info-outline">Explorer</button>
</div>
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
<h6 style="margin-top: 0"> Public key </h6>
{{parent.keys[line.address].publicExtendedKey}}
<h6> Private key </h6>
{{parent.keys[line.address].privateExtendedKey}}
</div>
</div>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="5">
<clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
<clr-datagrid class="datagrid-compact">
<clr-dg-column>Address</clr-dg-column>
<clr-dg-column>Balance</clr-dg-column>
<clr-dg-column>Pending</clr-dg-column>
<clr-dg-column>Estimate total</clr-dg-column>
<clr-dg-row *clrDgItems="let line of changeAddresses">
<clr-dg-cell style="word-break: break-all">{{line.address}}</clr-dg-cell>
<clr-dg-cell>{{line.confirmed.balance}}</clr-dg-cell>
<clr-dg-cell>{{line.unconfirmed.balance}}</clr-dg-cell>
<clr-dg-cell>{{line.total.balance}}</clr-dg-cell>
<clr-dg-row-detail *clrIfExpanded >
<div class="clr-row">
<div class="clr-col-3">
<button class="btn btn-info-outline">Explorer</button>
</div>
<div *ngIf="parent.keys[line.address] != null" class="clr-col-9" style="word-break:break-all">
<h6 style="margin-top: 0"> Public key </h6>
{{parent.keys[line.address].publicExtendedKey}}
<h6> Private key </h6>
{{parent.keys[line.address].privateExtendedKey}}
</div>
</div>
</clr-dg-row-detail>
</clr-dg-row>
<clr-dg-footer>
<clr-dg-pagination #pagination [clrDgPageSize]="5">
<clr-dg-page-size [clrPageSizeOptions]="[5,10,20,50,100]">per page</clr-dg-page-size>
{{pagination.firstItem + 1}} - {{pagination.lastItem + 1}}
of {{pagination.totalItems}}
</clr-dg-pagination>
</clr-dg-footer>
</clr-datagrid>
</div>
`
})
export class AddressViewerComponent implements OnInit {
regularAddresses = []
changeAddresses = []
keys = {}
parent:PluginsComponent
ngOnInit() { }
setParent(parent:PluginsComponent){
this.parent = parent
}
setRegularAddresses(addresses){
this.regularAddresses = addresses
}
setChangeAddresses(addresses){
this.changeAddresses = addresses
}
}
@@ -0,0 +1,508 @@
import { Component, OnInit, isDevMode, ViewChild, AfterViewInit } from '@angular/core';
import { WalletGeneratorComponent } from './wallet-generator.component';
import { WalletPickerComponent } from './wallet-picker.component';
import { HDKey } from "btc-hdkey"
import { AddressViewerComponent } from './address-viewer.component';
const fetch = require("node-fetch")
const coinselect = require('coinselect/accumulative')
import { TransactionBuilder } from 'bitcoinjs-lib'
import { networks } from 'bitcoinjs-lib'
let feeRate = 55 // satoshis per byte
const hdkey = new HDKey()
declare const fb
@Component({
selector: 'plugins', //!!!!
template: `
<wallet-generator></wallet-generator>
<!-- row 1 -->
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
<!-- wallet picker + send box + balance box -->
<div class="clr-row">
<!-- left column -->
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-5 clr-col-xl-5">
<!-- Wallet picker -->
<div class="clr-row">
<walletpicker class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12"></walletpicker>
</div>
<!-- /Wallet picker -->
<!-- Send box -->
<div class="clr-row">
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12">
<div class="card">
<div class="card-header">
Send
</div>
<div class="card-block" >
<input [(ngModel)]="sendTo" type="text" placeholder="To address" clrInput />
<input [(ngModel)]="sendAmount" type="number" min="0" placeholder="Amount" clrInput />
</div>
<div class="card-footer" >
<button class="btn btn-success-outline" (click)="createTx(sendTo, sendAmount)">send</button>
</div>
</div>
</div>
</div>
<!-- /Send Box -->
</div>
<!-- /left column -->
<!-- right column -->
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-7 clr-col-xl-7" >
<!-- Balance box -->
<div class="card">
<div class="card-header" style="word-break: break-all">
{{rootkey.publicExtendedKey}}
</div>
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
<clr-alert-item>
<span class="alert-text">
{{line.message}}
</span>
</clr-alert-item>
</clr-alert>
<div class="card-block" >
<h3 style="margin-top:12px">Balance: {{balance.confirmed}}</h3>
<p>
Unconfirmed: {{balance.unconfirmed}}<br />
Estimated total: {{balance.total}}
</p>
</div>
</div>
<!-- Balance box-->
</div>
<!-- right column -->
</div>
<!-- /row wallet picker + send box + balance box -->
<div class="clr-col-12 clr-col-sm-12 clr-col-md-12 clr-col-lg-12 clr-col-xl-12" style="padding-left:0px; padding-right: 0px">
<address-viewer></address-viewer>
</div>
</div>
<!-- /row 1 -->
`
})
export class PluginsComponent implements AfterViewInit {
wallets:{
[someLabel in string]:{
key:string
name:string
}
} = {}
txToSend:any
rootkey = {
publicExtendedKey: "",
privateExtendedKey: ""
}
keys = {}
regaddresses = []
changeaddresses = []
balance = {
confirmed: 0,
total: 0,
unconfirmed: 0
}
mode : {
net:network
} = {
net: "testnet"
}
sendto:string
amount:number
constructor(){
window["btc"] = () => this
}
@ViewChild(AddressViewerComponent, {static: false})
AddressViewerComponent:AddressViewerComponent
@ViewChild(WalletGeneratorComponent, {static: false})
WalletGeneratorComponent:WalletGeneratorComponent
@ViewChild(WalletPickerComponent, {static: false})
WalletPickerComponent:WalletPickerComponent
ngAfterViewInit(): void {
this.WalletGeneratorComponent.setParent(this)
this.WalletPickerComponent.setParent(this)
this.AddressViewerComponent.setParent(this)
//tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak
//tpubD9Do9r9dQoiLBNNXTEQVcyDzVzAoAFFu8btwzGNTvqoLpa6zk3UswXdq3zeArrakUu8huCtG1L5pTqNYNftanuutf57fn1C74rBzFybH1xe
this.loadKey("tprv8cXm1S7PGS2fHuLjZajuDZZsvxerzv4zZJJAhkLAWZzwz5rE7efHm31xsqpzfoGMHBNC8bkRcsjiMdmkSuNn5C1qNqk7rrk1pCuhk4HfVak")
}
addWallet(wallet){
/* store somewhere */
this.wallets[wallet.name] = wallet
this.WalletPickerComponent.setWallets(this.wallets)
}
showWizard(){
this.WalletGeneratorComponent.open()
}
loadKey(key:string){
this.wallets = {}
this.txToSend = null
this.sendto = ""
this.regaddresses = []
const t = hdkey.checkHDKey(key)
if(t){
this.mode = t
}
this.rootkey = hdkey.HDKeyFromExtendedKey(key)
this.refresh()
}
async refresh(){
this.keys = {}
this.changeaddresses = []
this.regaddresses = []
this.AddressViewerComponent.setChangeAddresses([])
this.AddressViewerComponent.setRegularAddresses([])
this.balance.total = 0
this.balance.unconfirmed = 0
this.balance.confirmed = 0
let p1 = this.updateChange()
let p2 = this.update()
let balances:any[] = await Promise.all([p1,p2])
balances.forEach(balance => {
this.balance.confirmed += balance.confirmed
this.balance.total += balance.total
this.balance.unconfirmed += balance.unconfirmed
})
console.log(this.changeaddresses)
this.AddressViewerComponent.setChangeAddresses(this.changeaddresses)
this.AddressViewerComponent.setRegularAddresses(this.regaddresses)
}
async getInfo(rootAddr: string, n = 10, skip = 0, changeAddresses = false): Promise<any>{
const t = hdkey.checkHDKey(rootAddr)
if(!t){
return false
}
let addresses: string[] = []
for(let i = skip; i < skip+n; i++){
let addr
if(changeAddresses){
addr = hdkey.deriveAddress(rootAddr, i, 'm/1/')
}else{
addr = hdkey.deriveAddress(rootAddr, i)
}
if(!addr){
return false
}
addresses.push(addr)
}
let lookupstr = addresses.join(',')
try{
let r = await fetch(api[t.net]+'/address/'+lookupstr, {
'method':'GET',
'mode':'cors',
'data': {limit: 200}
})
if(r.status !== 200){
return {success: false}
}
let json = (await r).json()
return json
}catch(e){
return {success: false}
}
}
async getTxs(address: string, type:network){
let r = await fetch(api[type]+'/address/'+address, {
'method':'GET',
'mode':'cors'
})
if(r.status !== 200){
return {success: false}
}
return (await r).json()
}
async getUnspentTxs(addresses:string[], type:network):Promise<any>{
let lookupstr = addresses.join(',')
try{
let r = await fetch(api[type]+'/address/'+lookupstr+'/unspent', {
'method':'GET',
'mode':'cors'
})
if(r.status !== 200){
return {success: false}
}
return (await r).json()
}catch(e){
return {success: false}
}
}
getKeyForAddress(keyName){
return this.keys[keyName]
}
async update(){
let n = 1
let regAddresses
while(true){
try{
regAddresses = await this.getInfo(this.rootkey.publicExtendedKey, 115*n, 115*(n-1))
}catch(e){
break
}
if(regAddresses.success === true){
if('addresses' in regAddresses){ // multiple
this.regaddresses = [...this.regaddresses, ...regAddresses.addresses]
}
if('address' in regAddresses){
this.regaddresses.push(regAddresses.address)
}
n++
}else{
console.log('No more balance found after attempting ', 115*n, 'addresses')
if(this.rootkey.privateExtendedKey != null){
for(let i = 0; i < 115*n; i++){
const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i)
const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net)
this.keys[addrN] = keyN
}
}
break
}
}
if(this.regaddresses.length === 0){
const addr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, 0)
this.regaddresses.push({
address: addr,
confirmed: {balance: 0},
unconfirmed: {balance: 0},
total: {balance: 0},
})
return {
total: 0,
unconfirmed: 0,
confirmed: 0
}
}
const resMap: any = {}
this.regaddresses.forEach(a => {
resMap[a.address] = a
})
const confirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
return accum + parseFloat(obj.confirmed.balance)
}, 0)
const unconfirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
return accum + parseFloat(obj.unconfirmed.balance)
}, 0)
return {
total: confirmed+unconfirmed,
unconfirmed: unconfirmed,
confirmed: confirmed
}
}
async updateChange(){
let n = 1
let res
while(true){
try{
res = await this.getInfo(this.rootkey.publicExtendedKey, 115*n, 115*(n-1), true)
}catch(e){
break
}
if(res.success === true){
if('addresses' in res){ // multiple
this.changeaddresses = [...this.changeaddresses, ...res.addresses]
}
if('address' in res){
this.changeaddresses.push(res.address)
}
n++
}else{
console.log('No more balance found after attempting ', 115*n, 'addresses')
if(this.rootkey.privateExtendedKey != null){
for(let i = 0; i < 115*n; i++){
const keyN = hdkey.deriveKey(this.rootkey.privateExtendedKey, i, 'm/1/')
const addrN = hdkey.addressFromHDKey(keyN._publicKey, this.mode.net)
this.keys[addrN] = keyN
}
}
break
}
}
if(this.changeaddresses.length === 0){
const addr = hdkey.deriveAddress(this.rootkey.publicExtendedKey, 0, 'm/1/')
this.changeaddresses.push({
address: addr,
confirmed: {balance: 0},
unconfirmed: {balance: 0},
total: {balance: 0},
})
return {
total: 0,
unconfirmed: 0,
confirmed: 0
}
}
const resMap: any = {}
this.changeaddresses.forEach(a => {
resMap[a.address] = a
})
const confirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
return accum + parseFloat(obj.confirmed.balance)
}, 0)
const unconfirmed = <number>Object.values(resMap).reduce((accum:any, obj:any) => {
return accum + parseFloat(obj.unconfirmed.balance)
}, 0)
return {
confirmed: confirmed,
unconfirmed: unconfirmed,
total: unconfirmed + confirmed
}
}
async createTx(target:string, value:number){
console.log(target, value)
if(target == null || typeof target !== 'string' || target === '' || value == null || value <= 0 ){
//this.error = 'Bad inputs. Expected bitcoin address and number'
return
}
if(value > this.balance.confirmed){
//this.error = 'Insufficient funds'
return
}
let addresses: string[] = this.regaddresses.map(a => a.address)
addresses = [...addresses, ...this.changeaddresses.map(a => a.address)]
const txs = await this.getUnspentTxs(addresses, this.mode.net)
if(txs == null || txs.success !== true || txs.unspent.length === 0){
//this.error = 'No UTXOs found'
return
}
const utxos = txs.unspent.map(t => {
return {
address: t.addresses[0],
txId: t.txid,
vout: t.n,
value: t.value * 100000000
}
})
let { inputs, outputs, fee } = coinselect(utxos, [{address: target, value: value*100000000}], feeRate)
// .inputs and .outputs will be undefined if no solution was found
if (!inputs || !outputs){
//this.error = 'Could not build a valid transaction'
return
}
const txb = new TransactionBuilder(networks[this.mode.net])
txb.setVersion(1)
inputs.forEach((input) => {
txb.addInput(input.txId, input.vout)
})
outputs.forEach((output) => {
if (!output.address) {
output.address = hdkey.deriveAddress(this.rootkey.publicExtendedKey, this.changeaddresses.length, 'm/1/')
}
txb.addOutput(output.address, output.value)
})
inputs.forEach((input, i) => {
txb.sign(i, this.keys[input.address])
})
this.txToSend = txb.build()
this.sendTx()
}
async sendTx(){
if(this.txToSend == null){
//this.error = 'No transaction to send!'
return
}
let url
if(this.mode.net === 'mainnet'){
url = 'https://api.smartbit.com.au/v1/blockchain/pushtx'
}else{
url = 'https://testnet-api.smartbit.com.au/v1/blockchain/pushtx'
}
let r = await fetch(url, {
'method':'POST',
'body': JSON.stringify({'hex': this.txToSend.toHex()})
})
this.txToSend = null
if(r.status === 200){
console.log(r)
this.refresh()
}else{
//this.error = 'The API '+url+' returned a non-200 error code '+r.status+' '+r.statusText
console.log(r)
}
}
}
type network = 'testnet' | 'mainnet'
type keyType = 'public' | 'private'
type bip32Keypair = { publicExtendedKey: string, privateExtendedKey: string }
const api = {
testnet: 'https://testnet-api.smartbit.com.au/v1/blockchain',
mainnet: 'https://api.smartbit.com.au/v1/blockchain'
}
@@ -0,0 +1,265 @@
import { Component, OnInit, isDevMode, ViewChild } from '@angular/core';
import { ClrWizard } from '@clr/angular';
import { HDKey } from "btc-hdkey"
import { PluginsComponent } from './btc.component';
declare const fb
@Component({
selector: 'wallet-generator', //!!!!
template: `
<clr-wizard #wizard [(clrWizardOpen)]="mdOpen" [clrWizardForceForwardNavigation]="true" clrWizardSize="lg">
<clr-wizard-title>Bitcoin wallet generator and importer</clr-wizard-title>
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
<clr-wizard-button [type]="'previous'">Back</clr-wizard-button>
<clr-wizard-button [type]="'next'">Next</clr-wizard-button>
<clr-wizard-button [type]="'finish'">Finish</clr-wizard-button>
<clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
<ng-template clrPageTitle>Menemonic/BIP39 (optional)</ng-template>
<ng-template clrPageNavTitle>
Menemonic/BIP39
</ng-template>
<p>
A menemonic is a collection of words that is supposed to be easier to remember than a complex seed. It may be used to reconstruct the private key.
</p>
<p>
Enter your menemonic below or generate a new one.
</p>
<textarea [(ngModel)]="menemonic" class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px">{{menemonic}}</textarea>
<div class="clr-row">
<clr-checkbox-wrapper class="clr-col">
<input type="checkbox" clrCheckbox value="testnet" name="testnet1" [(ngModel)]="testnet" />
<label>testnet</label>
</clr-checkbox-wrapper>
<button class="btn btn-outline-success clr-col" (click)="generateMenemonic()">{{"Generate"}}</button>
</div>
<ng-template clrPageButtons>
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
<clr-wizard-button [type]="'menemonic-next'">{{menemonic.length==0?"Skip":"Next"}}</clr-wizard-button>
</ng-template>
</clr-wizard-page>
<clr-wizard-page (clrWizardPageCustomButton)="doCustomClick($event)">
<ng-template clrPageTitle>Seed phrase (optional)</ng-template>
<ng-template clrPageNavTitle>
Seed phrase
</ng-template>
<p>
If you entered a menemonic in the previous step, a seed was generated from it.
</p>
<p>
If you would like to use your own seed enter it below. Use something sufficiently hard to guess.
</p>
<textarea class="clr-textarea" (ngModelChange)="updateSeed()" [(ngModel)]="seed" style="margin-top: 24px; width: 100%; height: 200px">{{seed}}</textarea>
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox value="testnet" name="testnet2" [(ngModel)]="testnet" />
<label>testnet</label>
</clr-checkbox-wrapper>
<ng-template clrPageButtons>
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
<clr-wizard-button [type]="'back'">Back</clr-wizard-button>
<clr-wizard-button [type]="'seed-next'">{{seed.length==0?"Skip":"Next"}}</clr-wizard-button>
</ng-template>
</clr-wizard-page>
<clr-wizard-page [clrWizardPageNextDisabled]="keyInvalid" (clrWizardPageCustomButton)="doCustomClick($event)">
<ng-template clrPageTitle>Extended Key/BIP32</ng-template>
<p>
Accepts keys in the xpub and xprv (BIP32) format
</p>
<textarea [(ngModel)]="key" (ngModelChange)="updateKey()"class="clr-textarea" style="margin-top: 24px; width: 100%; height: 200px"></textarea>
</clr-wizard-page>
<clr-wizard-page class="break-word" (clrWizardPageCustomButton)="doCustomClick($event)">
<ng-template clrPageTitle>Confirm</ng-template>
<div class="alert alert-danger" role="alert">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
</div>
<span class="alert-text">
Do not give any of this information to other people! Even your public key holds the power to correlate all your addresses back to you!
</span>
</div>
</div>
</div>
<div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-warning" role="alert">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
</div>
<span class="alert-text">
WARNING: A private key is not required for operating frontblock
</span>
</div>
</div>
</div>
<div *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0" class="alert alert-info" role="alert">
<div class="alert-items">
<div class="alert-item static">
<div class="alert-icon-wrapper">
<clr-icon class="alert-icon" shape="exclamation-circle"></clr-icon>
</div>
<span class="alert-text">
HINT: You can exclusively store the public key by going back one step and importing the xpub you just created
</span>
</div>
</div>
</div>
<table class="table">
<thead>
<tr>
<th class="left">Type</th>
<th class="left">Value</th>
</tr>
</thead>
<tbody>
<tr *ngIf="menemonic.length != 0">
<td class="left"> menemonic</td>
<td class="left">{{menemonic}}</td>
</tr>
<tr *ngIf="seed.length != 0">
<td class="left"> seed</td>
<textarea rows="3" class="clr-textarea left" [ngModel]="seed" style="width: 100%"></textarea>
</tr>
<tr>
<td class="left"> xpub</td>
<textarea rows="3" class="clr-textarea left" [ngModel]="keypair.publicExtendedKey" style="width: 100%"></textarea>
</tr>
<tr *ngIf="keypair.privateExtendedKey != null && keypair.privateExtendedKey.length != 0">
<td class="left"> xprv</td>
<textarea rows="3" class="clr-textarea left" [ngModel]="keypair.privateExtendedKey" style="width: 100%"></textarea>
</tr>
</tbody>
</table>
<clr-checkbox-wrapper>
<input type="checkbox" clrCheckbox value="option1" name="options" />
<label>I understand this is important</label>
</clr-checkbox-wrapper>
<ng-template clrPageButtons>
<clr-wizard-button [type]="'cancel'">Cancel</clr-wizard-button>
<clr-wizard-button class="btn btn-success" [type]="'myfinish'">Finish</clr-wizard-button>
</ng-template>
</clr-wizard-page>
</clr-wizard>
`
})
export class WalletGeneratorComponent implements OnInit {
@ViewChild("wizard", {static: false}) wizard: ClrWizard;
key:string = ""
keyInvalid = true
accept = false
seed:string = ""
mdOpen: boolean = false
checkKey: boolean = true
menemonic:string = ""
keypair: any = {}
testnet: boolean = false
profileName
private hdKey = new HDKey()
private parent:PluginsComponent
ngOnInit() { }
open(){
this.key = ""
this.keyInvalid = true
this.seed = ""
this.checkKey = true
this.menemonic = ""
this.keypair = {}
this.testnet = false
this.wizard.reset()
this.mdOpen = true
}
setParent(parent:PluginsComponent){
this.parent = parent
}
async generateMenemonic(){
this.menemonic = this.hdKey.randomMenemonic()
}
async seedFromMenemonic(){
try{
this.seed = this.hdKey.seedFromMenemonic(this.menemonic)
}catch(e){
/* invalid menemonic */
console.log(e)
}
}
async accountFromSeed(){
this.keypair = this.hdKey.deriveAccount(this.seed, this.testnet?"testnet":"mainnet", 0)
console.log(this.keypair)
this.key = this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey
this.keyInvalid = false
}
async importKey(){
this.hdKey.HDKeyFromExtendedKey(this.key)
}
checkBip32(){
return this.hdKey.checkHDKey(this.keypair.privateExtendedKey != null?this.keypair.privateExtendedKey:this.keypair.publicExtendedKey)
}
finish(){
this.parent.addWallet({
name: this.key.substr(4,15),
key: this.key
})
}
public doCustomClick(buttonType: string): void {
if ("menemonic-next" === buttonType) {
if(this.menemonic.length != 0){
this.seedFromMenemonic()
}
this.wizard.next();
}
if ("myfinish" === buttonType) {
this.finish()
this.wizard.close()
}
if ("seed-next" === buttonType) {
if(this.seed.length != 0){
this.accountFromSeed()
}
this.wizard.next();
}
if ("back" === buttonType) {
this.wizard.previous();
}
}
updateSeed(){
this.menemonic = ""
}
updateKey(){
this.keyInvalid = true
this.menemonic = ""
this.seed = ""
let key
try{
key = this.hdKey.HDKeyFromExtendedKey(this.key)
}catch(e){
/* invalid key */
console.log("bad key")
}
if(key){
this.keypair = key
this.keyInvalid = false
}
}
}
@@ -0,0 +1,50 @@
import { Component, OnInit, isDevMode } from '@angular/core';
import { PluginsComponent } from './btc.component';
declare const fb
@Component({
selector: 'walletpicker', //!!!!
template: `
<div class="card" style="overflow:hidden">
<div class="card-header">
Wallets
</div>
<clr-alert *ngFor="let entry of alerts" [clrAlertType]="line.severity">
<clr-alert-item>
<span class="alert-text">
{{line.message}}
</span>
</clr-alert-item>
</clr-alert>
<clr-tree-node style="margin-top:12px; margin-bottom:12px">
<a style="cursor:pointer" (click)="parent.showWizard()"><clr-icon shape="download" class="is-solid"></clr-icon> New / Import</a>
</clr-tree-node>
<clr-tree-node *ngFor="let line of wallets">
<a style="cursor:pointer" (click)="parent.loadKey(line.key)">
<clr-icon *ngIf="line.key.startsWith('xpub') || line.key.startsWith('tpub')" shape="lock" class="is-solid"></clr-icon>
<clr-icon *ngIf="line.key.startsWith('xprv') || line.key.startsWith('tprv')" shape="key" class="is-solid"></clr-icon>
{{line.name}}
</a>
</clr-tree-node>
</div>
`
})
export class WalletPickerComponent implements OnInit {
private parent:PluginsComponent
wallets = []
setWallets(wallets){
this.wallets = Object.values(wallets)
}
setParent(parent:PluginsComponent){
this.parent = parent
}
ngOnInit() { }
}
@@ -0,0 +1,60 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { PluginsComponent } from './btc/btc.component';
import { WalletPickerComponent } from './btc/wallet-picker.component';
import { AddressViewerComponent } from './btc/address-viewer.component';
import { FrontendPlugin, SidebarEntries } from 'frontblock-generic/Plugin';
import { ClarityModule } from '@clr/angular';
import { FormsModule } from '@angular/forms';
import { WalletGeneratorComponent } from './btc/wallet-generator.component';
@NgModule({
imports: [
FormsModule,
ClarityModule,
CommonModule,
RouterModule.forChild([
{path: "btc", component: PluginsComponent},
]),
],
exports: [RouterModule],
declarations: [
PluginsComponent,
WalletPickerComponent,
AddressViewerComponent,
WalletGeneratorComponent
]
})
export class PluginModule implements FrontendPlugin{
getSidebarEntry(): SidebarEntries {
return {
icon: "wallet",
text: "Wallets",
parentRoute: "wallet",
links: [{
route: "btc",
text: "BTC Wallet"
},{
route: "ltc",
text: "LTC Wallet"
},{
route: "eth",
text: "ETH Wallet"
},{
route: "xrp",
text: "XRP Wallet"
},{
route: "xlm",
text: "XLM Wallet"
},{
route: "settings",
text: "Settings"
}]
}
}
}
@@ -1,3 +1,10 @@
.heightmax{ .heightmax{
height: 100% height: 100%
} }
.break-word {
word-wrap: break-word;
}
.monospace {
font-family: monospace;
font-size: 90%
}
@@ -1,7 +1,6 @@
import { Component, ChangeDetectorRef, NgZone, ViewChild, OnInit, AfterContentInit } from '@angular/core'; import { Component, ChangeDetectorRef, NgZone, ViewChild, OnInit, AfterContentInit, isDevMode } from '@angular/core';
import { SidebarComponent } from './sidebar/sidebar.component'; import { SidebarComponent } from './sidebar/sidebar.component';
import { SidebarEntryService } from './sidebar-entry-service.service'; import { SidebarEntryService } from './sidebar-entry-service.service';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
@@ -15,6 +14,9 @@ export class AppComponent implements AfterContentInit {
constructor(private zone:NgZone, private sidebarService: SidebarEntryService){ constructor(private zone:NgZone, private sidebarService: SidebarEntryService){
window["refresh"] = setInterval(() => zone.run(()=>{ /*this triggers an angular reload*/}), 200) window["refresh"] = setInterval(() => zone.run(()=>{ /*this triggers an angular reload*/}), 200)
if(isDevMode()){
require("../assets/FrontblockLib")
}
} }
ngAfterContentInit(){ ngAfterContentInit(){
@@ -1,3 +1,3 @@
<div class="content-area clr-col-12 clr-col-sm-12 clr-col-lg-12 clr-col-xl-12"> <div style="overflow:hidden" class="content-area clr-col-12 clr-col-sm-12 clr-col-lg-12 clr-col-xl-12">
<router-outlet></router-outlet> <router-outlet></router-outlet>
</div> </div>
@@ -39,12 +39,11 @@ import { AfterViewInit, Component, ViewChild } from '@angular/core';
import { SidebarComponent } from '../sidebar/sidebar.component'; import { SidebarComponent } from '../sidebar/sidebar.component';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { FrontendPlugin, SidebarEntries, SidebarEntry } from "frontblock-generic/Plugin" import { FrontendPlugin, SidebarEntries, SidebarEntry } from "frontblock-generic/Plugin"
import { SidebarEntryService } from '../sidebar-entry-service.service';
import { environment } from "../../environments/environment" import { environment } from "../../environments/environment"
const fb = environment.production ? window["fb"] : { const fb = environment.production ? window["fb"] : {
PluginManager: { PluginManager: {
getLoadedPluginNames: () => ["ApiClient", "PluginManager"] getLoadedPluginNames: () => ["ApiClient", "PluginManager", "Wallet"]
} }
} }
@@ -6,7 +6,7 @@ import { Component, OnInit, isDevMode } from '@angular/core';
styleUrls: ['./header-bar.component.scss'] styleUrls: ['./header-bar.component.scss']
}) })
export class HeaderBarComponent implements OnInit { export class HeaderBarComponent implements OnInit {
devmode = isDevMode()?"{ DEV }":"" devmode = isDevMode()?"{ ng-DEV }":""
constructor() { } constructor() { }
ngOnInit() { ngOnInit() {
@@ -0,0 +1 @@
./FrontblockLib.js
File diff suppressed because one or more lines are too long
+4
View File
@@ -61,3 +61,7 @@ import 'zone.js/dist/zone'; // Included with Angular CLI.
/*************************************************************************************************** /***************************************************************************************************
* APPLICATION IMPORTS * APPLICATION IMPORTS
*/ */
// Add global to window, assigning the value of window itself.
(window as any).global = window;
global.Buffer = global.Buffer || require('buffer').Buffer;
global.process = global.process || require('process')