Commit 5363615f authored by Dirk Luijk's avatar Dirk Luijk

Polling + form validation + styling improvements

parent 3537477f
......@@ -22,63 +22,43 @@ Stap voor stap gaan we de blockchain opbouwen. We gaan de volgende functionalite
## Backend
## Initiele build
Nadat je het project uit git hebt uitgecheckt, dien je eerst de backend te builden:
Nadat je het project uit github hebt uitgecheckt, dien je het project eenmalig te bouwen:
```
gradlew build
```
Dit duurt ongeveer 1 minuut. Je kan nu de backend starten met:
Dit duurt ongeveer 2 minuten. Je kan nu het project starten:
```
gradlew bootRun
```
Dit start een SpringBoot REST applicatie op poort 8080 op. De volgende endpoints zijn beschikbaar:
Dit start een SpringBoot applicatie op poort 8080 op. Ga naar http://localhost:8080/index.html om de UI te zien. Hier zie je de volgende tabs:
* `GET /api/pendingtransactions`: alle transacties die nog niet in de blockchain opgenomen zijn
* `POST /api/blockchain`: nieuwe transactie het netwerk inschieten
* `GET /api/wallet/{walletId}`: de huidige blockchain
* `GET /api/peers`: overzicht welke peers er allemaal aan deze node verbonden zijn
* `POST /api/mine`: een mining actie starten
Uiteraard kan je middels Postman deze endpoints ook benaderen. De frontend is dus alleen ter convenience.
## Frontend
Ga naar de frontend directory (`cd frontend`) en installeer je dependencies:
```
npm install
```
of, als je `yarn` geïnstalleerd hebt staan:
```
yarn
```
Dit kan enkele minuten duren. Start nu de frontend op:
1. *Pending transactions*. Hier zie je alle transacties die nog niet in de blockchain opgenomen zijn.
2. *New transaction*. Hier kan je een nieuwe transactie het netwerk inschieten.
3. B*lockchain*. Hier zie je de huidige blockchain.
4. *Peers*. Hier staat een overzicht welke peers er allemaal aan deze node verbonden zijn.
5. *Mine*. Hier kan je een mining actie starten.
6. *Wallet*. Hier kan je een wallet bekijken.
```
npm start
```
Dit correspondeert met de volgende endpoints:
of, als je `yarn` geïnstalleerd hebt staan:
```
yarn run start
GET /api/pendingtransactions
POST /api/newtransaction
GET /api/blockchain
GET /api/wallet/{walletId}
GET /api/peers
POST /api/mine
```
Ga naar http://localhost:4200 om de UI te zien. Hier zie je de volgende tabs:
Uiteraard kan je middels Postman deze api's ook benaderen. De Frontend is dus alleen ter convenience.
1. *Pending transactions*
2. *New transaction*
3. *Blockchain*
4. *Peers*
5. *Mine*
6. *Wallet*. Hier kan je een wallet bekijken.
Deze corresponderen met de API endpoints.
## Bouwen van het block
......
......@@ -13,6 +13,8 @@ buildscript {
plugins {
id 'org.springframework.boot' version '1.5.9.RELEASE'
id 'com.moowork.node' version '1.2.0'
}
apply plugin: 'java'
......@@ -23,6 +25,39 @@ group = 'nl.craftsmen.craftsmencoinnode'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = 1.8
processResources {
from ('frontend/dist/') {
into 'public'
}
}
// configure gradle-node-plugin
node {
//version = '8.9.4'
//npmVersion = '5.6.0'
download = true
workDir = file("${project.projectDir}/frontend/node")
nodeModulesDir = file("${project.projectDir}/frontend")
}
// clean node/node_modules/dist
task npmClean(type: Delete) {
final def webDir = "${rootDir}/frontend"
delete "${webDir}/node"
delete "${webDir}/node_modules"
delete "${webDir}/dist"
delete "${webDir}/coverage"
delete "${webDir}/build"
}
clean.dependsOn(npmClean)
build.dependsOn(npm_install)
build.dependsOn(npm_run_build)
//processResources.dependsOn(npm_run_build)
repositories {
mavenCentral()
}
......@@ -34,6 +69,7 @@ bootRun {
args += ["--server.port=${project.port}"]
}
dependencies {
compile('org.springframework.boot:spring-boot-starter-web')
compile 'commons-codec:commons-codec:1.10'
......@@ -41,4 +77,6 @@ dependencies {
compile group: 'org.apache.httpcomponents', name: 'httpclient', version: '4.5.4'
compile group: 'io.springfox', name: 'springfox-swagger-ui', version: '2.7.0'
compile group: 'io.springfox', name: 'springfox-swagger2', version: '2.7.0'
}
......@@ -8,6 +8,7 @@ import { MessageService } from '../messages/message.service';
// import {TRANSACTIONS} from './mock-transactions';
// import {of} from 'rxjs/observable/of';
import 'rxjs/add/operator/do';
@Injectable()
export class TransactionService {
......@@ -18,8 +19,9 @@ export class TransactionService {
// }
getTransactions(): Observable<Transaction[]> {
this.log('TransactionService: fetching transactions heroes');
this.log('Fetching transactions...');
return this.http.get<Transaction[]>('/api/pendingtransactions')
.do(transactions => this.log(`Got ${transactions.length} transactions`));
}
save(transaction: Transaction): Observable<TransactionResult> {
......@@ -28,8 +30,7 @@ export class TransactionService {
.do(result => this.log(result.message));
}
/** Log a HeroService message with the MessageService */
private log(message: string) {
this.messageService.add('TransactionService: ' + message);
this.messageService.add(message);
}
}
......@@ -2,7 +2,7 @@
<h1>CraftsCoin</h1>
<p class="lead">Node interface</p>
<nav>
<nav class="mt-5">
<ul class="nav nav-tabs">
<li class="nav-item">
<a class="nav-link" [routerLink]="['dashboard']" routerLinkActive="active">Dashboard</a>
......@@ -16,6 +16,12 @@
</ul>
</nav>
<router-outlet></router-outlet>
<app-messages></app-messages>
<div class="row">
<div class="col-md-6">
<router-outlet></router-outlet>
</div>
<div class="col-md-6">
<app-messages></app-messages>
</div>
</div>
</div>
<h2>Top transactions</h2>
<div *ngIf="transactions.length === 0" class="alert alert-info">
No transactions available.
</div>
<div *ngIf="transactions.length === 0">No transactions available.</div>
<ul *ngIf="transactions.length > 0">
<li *ngFor="let transaction of transactions">
......
......@@ -17,6 +17,6 @@ export class DashboardComponent implements OnInit {
getTransactions(): void {
this.transactionService.getTransactions()
.subscribe(transactions => this.transactions = transactions.slice(1, 2));
.subscribe(transactions => this.transactions = transactions.slice(0, 2));
}
}
import { Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';
import { Observable } from 'rxjs/Observable';
import { ReplaySubject } from 'rxjs/ReplaySubject';
export interface Message {
text: string;
time: Date;
}
@Injectable()
export class MessageService {
messages$: Observable<string[]>;
messages$: Observable<Message[]>;
private messages: string[] = [];
private messagesSubject = new ReplaySubject<string[]>();
private messages: Message[] = [];
private messagesSubject = new ReplaySubject<Message[]>();
constructor() {
this.messages$ = this.messagesSubject.asObservable();
}
add(message: string): void {
this.messages.push(message);
this.messages.push({
text: message,
time: new Date(),
});
this.messagesSubject.next(this.messages);
}
......
<h2>Messages</h2>
<div *ngIf="messages.length === 0" class="alert alert-info">
No messages.
</div>
<div *ngIf="messages.length === 0">No messages.</div>
<div *ngIf="messages.length > 0">
<button class="btn btn-secondary" (click)="clear()">Clear messages</button>
<pre>
<div *ngFor="let message of messages">{{ message }}</div>
</pre>
<pre><div *ngFor="let message of messages">{{ message.time | date: 'mediumTime' }} - {{ message.text }}</div></pre>
<button class="btn btn-secondary btn-sm" (click)="clear()">Clear messages</button>
</div>
import { Component, OnInit } from '@angular/core';
import { MessageService } from './message.service';
import { MessageService, Message } from './message.service';
@Component({
selector: 'app-messages',
templateUrl: './messages.component.html'
})
export class MessagesComponent implements OnInit {
messages: string[] = [];
messages: Message[] = [];
constructor(private messageService: MessageService) {}
ngOnInit(): void {
this.messageService.messages$.subscribe(messages => this.messages = messages);
this.messageService.messages$.subscribe(messages => this.messages = messages.slice(-20));
}
clear(): void {
......
<h2>New transaction</h2>
<table>
<tr>
<td>from:</td>
<td><input [(ngModel)]="newTransaction.from" placeholder="from" class="form-control"></td>
</tr>
<tr>
<td>to:</td>
<td><input [(ngModel)]="newTransaction.to" placeholder="to" class="form-control"></td>
</tr>
<tr>
<td>amount:</td>
<td><input [(ngModel)]="newTransaction.amount" placeholder="amount" class="form-control"></td>
</tr>
</table>
<form #form="ngForm" (submit)="submit()">
<div class="form-group">
<label for="from">From</label>
<input type="text" name="from" [(ngModel)]="newTransaction.from" required class="form-control" id="from" placeholder="From">
</div>
<div class="form-group">
<label for="to">To</label>
<input type="text" name="to" [(ngModel)]="newTransaction.to" required class="form-control" id="to" placeholder="To">
</div>
<div class="form-group">
<label for="amount">Amount</label>
<input type="number" name="amount" [(ngModel)]="newTransaction.amount" required class="form-control" id="amount" placeholder="Amount">
</div>
<button class="btn btn-primary" (click)="save()">Save</button>
<button class="btn btn-primary" type="submit" [disabled]="form.pristine || form.invalid">Save</button>
</form>
import { Component, OnInit } from '@angular/core';
import { Component } from '@angular/core';
import {Transaction} from '../api/transaction';
import { TransactionService } from '../api/transaction.service';
import {MessageService} from '../messages/message.service';
@Component({
selector: 'app-transaction',
templateUrl: './transaction.component.html'
......@@ -9,9 +9,9 @@ import {MessageService} from '../messages/message.service';
export class TransactionComponent {
newTransaction: Transaction = new Transaction();
constructor(private transactionService: TransactionService, private messageService: MessageService) { }
constructor(private transactionService: TransactionService) { }
save(): void {
submit(): void {
this.transactionService
.save(this.newTransaction)
.subscribe();
......
<h2>Pending transactions</h2>
<div *ngIf="transactions.length === 0" class="alert alert-info">
No transactions yet.
</div>
<div *ngIf="transactions.length === 0">No transactions yet.</div>
<table class="table" *ngIf="transactions.length > 0">
<tr>
......
import { Component, OnInit } from '@angular/core';
import { Component, OnInit, OnDestroy } from '@angular/core';
import { Observable } from 'rxjs/Observable';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/observable/timer';
import 'rxjs/add/operator/switchMap';
import { Transaction } from '../api/transaction';
import { TransactionService } from '../api/transaction.service';
......@@ -7,17 +12,24 @@ import { TransactionService } from '../api/transaction.service';
selector: 'app-transactions',
templateUrl: './transactions.component.html'
})
export class TransactionsComponent implements OnInit {
export class TransactionsComponent implements OnInit, OnDestroy {
selectedTransaction: Transaction;
transactions: Transaction[] = [];
private subscription: Subscription;
constructor(private transactionService: TransactionService) {}
ngOnInit() {
this.transactionService.getTransactions()
ngOnInit(): void {
this.subscription = Observable.timer(0, 5000)
.switchMap(() => this.transactionService.getTransactions())
.subscribe(transactions => this.transactions = transactions);
}
ngOnDestroy(): void {
this.subscription.unsubscribe();
}
onSelect(transaction: Transaction): void {
this.selectedTransaction = transaction;
}
......
/* Application-wide Styles */
//@import "~bootswatch/dist/lumen/variables";
@import "~bootswatch/dist/flatly/variables";
@import "~bootstrap/scss/bootstrap";
//@import "~bootswatch/dist/lumen/bootswatch";
@import "~bootswatch/dist/flatly/bootswatch";
body {
padding-top: 2rem;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment