From 9e30bdcf8a2fa079dd7e5750abb92df85f88ca4f Mon Sep 17 00:00:00 2001
From: cgeek <cem.moreau@gmail.com>
Date: Sun, 19 May 2019 11:38:18 +0200
Subject: [PATCH] [enh] Forkview: new view to display fork informations

---
 back/webmin/graphql/resolvers/BigResolver.ts  |  14 +-
 back/webmin/graphql/types/BlockType.ts        |   2 +-
 back/webmin/graphql/types/ChainType.ts        |   8 +
 .../types/transform/dbblock.transform.ts      |  40 +++
 back/webmin/queries/gql-forks.ts              |  21 ++
 src/components/ForkGraph.vue                  | 249 ++++++++++++++++++
 src/components/svg/SvgBlock.vue               |  47 ++++
 src/components/svg/SvgChain.vue               |  82 ++++++
 src/lib/services/webmin.service.ts            |  47 +++-
 src/lib/utils/blockstamp.ts                   |   3 +
 src/views/Home.vue                            |  37 +--
 src/views/home/Forkview.vue                   | 116 ++++++++
 src/views/home/Overview.vue                   |  12 +-
 src/vue-modules/register-router.ts            |   7 +-
 src/vue-modules/register-store.ts             |   1 -
 tsconfig.json                                 |   1 +
 16 files changed, 650 insertions(+), 37 deletions(-)
 create mode 100644 back/webmin/graphql/types/ChainType.ts
 create mode 100644 back/webmin/graphql/types/transform/dbblock.transform.ts
 create mode 100644 back/webmin/queries/gql-forks.ts
 create mode 100644 src/components/ForkGraph.vue
 create mode 100644 src/components/svg/SvgBlock.vue
 create mode 100644 src/components/svg/SvgChain.vue
 create mode 100644 src/lib/utils/blockstamp.ts
 create mode 100644 src/views/home/Forkview.vue

diff --git a/back/webmin/graphql/resolvers/BigResolver.ts b/back/webmin/graphql/resolvers/BigResolver.ts
index fee2914..3808fd7 100644
--- a/back/webmin/graphql/resolvers/BigResolver.ts
+++ b/back/webmin/graphql/resolvers/BigResolver.ts
@@ -2,7 +2,7 @@ import {BlockType} from '../types/BlockType'
 import {WS2PHeadType} from '../types/WS2PHeadType'
 import {WS2PInfosType} from '../types/WS2PInfosType'
 import {Resolver} from 'type-graphql/dist/decorators/Resolver'
-import {Query} from 'type-graphql'
+import {Int, Query} from 'type-graphql'
 import {gqlNodeStart, gqlNodeState, gqlStopAndResetData} from '../../queries/gql-node-state'
 import {ApplicationContext} from '../di/application-context'
 import {gqlCurrent} from '../../queries/gql-current'
@@ -11,6 +11,8 @@ import {gqlWs2pInfos} from '../../queries/gql-ws2p'
 import {gqlIsSyncStarted, gqlSynchronize} from '../../queries/gql-synchronize'
 import {gqlUid} from '../../queries/gql-uid'
 import {Arg} from 'type-graphql/dist/decorators/Arg'
+import {getForks, getMainChain} from '../../queries/gql-forks'
+import {ChainType} from '../types/ChainType'
 
 @Resolver()
 export class BigResolver {
@@ -64,4 +66,14 @@ export class BigResolver {
   uid(@Arg("pub") pub: string): Promise<string|null> {
     return gqlUid(ApplicationContext.server)(null, { pub })
   }
+
+  @Query(type => ChainType)
+  getMainChain(@Arg("start", type => Int) start: number, @Arg("end", type => Int) end: number): Promise<ChainType> {
+    return getMainChain(ApplicationContext.server, start, end)
+  }
+
+  @Query(type => ChainType)
+  getForks(@Arg("start", type => Int) start: number, @Arg("end", type => Int) end: number): Promise<ChainType> {
+    return getForks(ApplicationContext.server, start, end)
+  }
 }
diff --git a/back/webmin/graphql/types/BlockType.ts b/back/webmin/graphql/types/BlockType.ts
index a78b74b..8b0d939 100644
--- a/back/webmin/graphql/types/BlockType.ts
+++ b/back/webmin/graphql/types/BlockType.ts
@@ -28,7 +28,7 @@ export class BlockType {
   @Field(type => [String]) certifications: string[]
   @Field(type => [BlockTransactionType]) transactions: BlockTransactionType[]
   @Field(type => Int) medianTime: number
-  @Field() nonce?: string
+  @Field() nonce?: number
   @Field({ nullable: true }) parameters?: string
   @Field(type => Int) monetaryMass: number
   @Field(type => Int, { nullable: true }) dividend?: number
diff --git a/back/webmin/graphql/types/ChainType.ts b/back/webmin/graphql/types/ChainType.ts
new file mode 100644
index 0000000..7e132e5
--- /dev/null
+++ b/back/webmin/graphql/types/ChainType.ts
@@ -0,0 +1,8 @@
+import {Field, Int, ObjectType} from 'type-graphql'
+import {BlockTransactionType} from './BlockTransactionType'
+import {BlockType} from './BlockType'
+
+@ObjectType()
+export class ChainType {
+  @Field(type => [BlockType]) blocks: BlockType[]
+}
diff --git a/back/webmin/graphql/types/transform/dbblock.transform.ts b/back/webmin/graphql/types/transform/dbblock.transform.ts
new file mode 100644
index 0000000..4ead785
--- /dev/null
+++ b/back/webmin/graphql/types/transform/dbblock.transform.ts
@@ -0,0 +1,40 @@
+import {DBBlock} from 'duniter/app/lib/db/DBBlock'
+import {BlockType} from '../BlockType'
+
+
+export function transformDBBlock(b: DBBlock): BlockType {
+  return {
+    version: b.version,
+    number: b.number,
+    currency: b.currency,
+    hash: b.hash,
+    inner_hash: b.inner_hash,
+    signature: b.signature,
+    previousHash: b.previousHash,
+    issuer: b.issuer,
+    previousIssuer: b.previousIssuer,
+    time: b.time,
+    powMin: b.powMin,
+    unitbase: b.unitbase,
+    membersCount: b.membersCount,
+    issuersCount: b.issuersCount,
+    issuersFrame: b.issuersFrame,
+    issuersFrameVar: b.issuersFrameVar,
+    identities: b.identities,
+    joiners: b.joiners,
+    actives: b.actives,
+    leavers: b.leavers,
+    revoked: b.revoked,
+    excluded: b.excluded,
+    certifications: b.certifications,
+    transactions: b.transactions,
+    medianTime: b.medianTime,
+    nonce: b.nonce,
+    parameters: b.parameters,
+    monetaryMass: b.monetaryMass,
+    dividend: b.dividend || undefined,
+    UDTime: b.UDTime,
+    writtenOn: b.writtenOn,
+    written_on: b.written_on,
+  }
+}
diff --git a/back/webmin/queries/gql-forks.ts b/back/webmin/queries/gql-forks.ts
new file mode 100644
index 0000000..762ea5a
--- /dev/null
+++ b/back/webmin/queries/gql-forks.ts
@@ -0,0 +1,21 @@
+import {Server} from 'duniter/server'
+import {transformDBBlock} from '../graphql/types/transform/dbblock.transform'
+import {ChainType} from '../graphql/types/ChainType'
+
+export async function getMainChain(server: Server, start: number, end: number): Promise<ChainType> {
+  const current = await server.dal.getCurrentBlockOrNull()
+  if (!current) {
+    return { blocks: [] }
+  }
+  const main = await server.dal.getBlocksBetween(start, end)
+  return { blocks: main.map(transformDBBlock) }
+}
+
+export async function getForks(server: Server, start: number, end: number): Promise<ChainType> {
+  const current = await server.dal.getCurrentBlockOrNull()
+  if (!current) {
+    return { blocks: [] }
+  }
+  const main = await server.dal.getPotentialForkBlocks(start, 0, end)
+  return { blocks: main.map(transformDBBlock) }
+}
diff --git a/src/components/ForkGraph.vue b/src/components/ForkGraph.vue
new file mode 100644
index 0000000..4816230
--- /dev/null
+++ b/src/components/ForkGraph.vue
@@ -0,0 +1,249 @@
+<style scoped>
+
+
+  line {
+    stroke-width: 3px;
+    stroke: red;
+  }
+
+</style>
+
+<template>
+  <svg :width="width" :height="height">
+    <g>
+      <svg-chain :chain="chain" :xOffset="xOffset" :yOffset="baseYoffset"
+                 :pCircleR="circleR"
+                 :pInterCircle="interCircle"></svg-chain>
+      <svg-chain v-for="(fork, i) in forkPoints" :chain="fork.blocks" :xOffset="fork.x" :yOffset="fork.y"
+                 :pCircleR="circleR"
+                 :pInterCircle="interCircle"
+                 :key="i"></svg-chain>
+      <line
+          v-for="l in forkLines"
+          :x1="l.p1.x"
+          :y1="l.p1.y"
+          :x2="l.p2.x"
+          :y2="l.p2.y"
+      ></line>
+    </g>
+  </svg>
+</template>
+
+<script lang="ts">
+  import Component from 'vue-class-component';
+  import Vue from 'vue';
+  import {Prop, Watch} from 'vue-property-decorator';
+  import SvgBlock from '@/components/svg/SvgBlock.vue';
+  import SvgChain from '@/components/svg/SvgChain.vue';
+  import {BlockType} from "../../back/webmin/graphql/types/BlockType";
+
+  const MAX_ROWS = 50
+  const MAX_COLS = 17
+
+  function initMatrix() {
+    return Array.from({ length: MAX_ROWS })
+      .map(i => Array.from({ length: MAX_COLS })
+        .map(j => false))
+  }
+
+  @Component({
+    components: {SvgChain, SvgBlock},
+  })
+  export default class extends Vue {
+
+    @Prop(Array) chain: BlockType[]
+    @Prop(Array) forks: BlockType[][]
+
+    maxCol = 0
+    maxRow = 0
+
+    matrix: boolean[][] = initMatrix()
+
+    forkPoints: ForkPoint[] = []
+    forkLines: Line[] = []
+
+    mounted() {
+      this.forkChanged()
+    }
+
+    @Watch('chain')
+    @Watch('forks')
+    forkChanged() {
+      if (this.chain.length) {
+        const mainFirst = this.chain[0].number
+        const mainLast = this.chain[this.chain.length - 1].number
+        const forkHighest = this.forks.reduce((max, f) => Math.max(max, f[f.length - 1].number), mainFirst)
+        this.maxCol = Math.max(mainLast, forkHighest) - mainFirst + 1
+        this.maxRow = this.forks.length + 1
+        this.matrix = initMatrix()
+        this.forkPoints = this.getNewForkPoints()
+        this.forkLines = this.getNewForkLines()
+      }
+    }
+
+    get width() {
+      return this.xOffset + (this.maxCol) * (2 * this.circleR + this.interCircle) + 'px'
+    }
+
+    get height() {
+      return (30 + (this.firstEmptyLine + 1) * this.lineHeight) + 'px'
+    }
+
+    get firstEmptyLine() {
+      for (let i = 0; i < this.matrix.length; i++) {
+        const lineHasData = this.matrix[i].reduce((redux, cell) => redux || cell, false)
+        if (!lineHasData) {
+          return i + 1
+        }
+      }
+      return this.matrix.length
+    }
+
+    get xOffset() {
+      return 26
+    }
+
+    get circleR() {
+      return 25
+    }
+
+    get interCircle() {
+      return 10
+    }
+
+    get lineHeight() {
+      return 60
+    }
+
+    get baseYoffset() {
+      return 30
+    }
+
+    getNewForkLines(): Line[] {
+      return this.forks
+        .map((fork, i) => ({
+          cell: this.getTargetCell(fork),
+          fork
+        }))
+        // Only forks with a target
+        .filter(data => data.cell.col !== -1 && data.cell.row !== -1)
+        .map(data => {
+          const cell = data.cell
+          const displayCell = this.forkPoints.filter(f => f.blocks[0].hash === data.fork[0].hash)[0]
+          const POS_ON_SOURCE = -45 * Math.PI / 180
+          const POS_ON_TARGET = 135 * Math.PI / 180
+          return {
+            p1: {
+              // Origin
+              x: (cell.col) * (this.circleR * 2 + this.interCircle) + this.circleR + this.circleR * Math.cos(POS_ON_SOURCE),
+              y: this.baseYoffset + this.circleR * Math.sin(-POS_ON_SOURCE),
+            },
+            p2: {
+              // Fork place
+              x: (cell.col + 1) * (this.circleR * 2 + this.interCircle) + this.circleR + this.circleR * Math.cos(POS_ON_TARGET),
+              y: (displayCell.yFinal + 1) * this.lineHeight + this.baseYoffset + this.circleR * Math.sin(-POS_ON_TARGET),
+            },
+          }
+      })
+    }
+
+    getNewForkPoints(): ForkPoint[] {
+      return this.forks.map(fork => {
+        const cell: Cell = this.getTargetCell(fork)
+        const y = this.getYoffset(cell, fork)
+        return {
+          x: this.getXoffset(cell, fork),
+          y: y.px,
+          yFinal: y.yFinal,
+          blocks: fork,
+        }
+      })
+    }
+
+    getTargetCell(fork: BlockType[]) {
+      const forkBlock = fork[0]
+      const allChains = [this.chain].concat(this.forks)
+      for (let i = 0; i < allChains.length; i++) {
+        let chain = allChains[i]
+        const diff = forkBlock.number - chain[0].number
+        if (diff > 0) {
+          const targetBlock = chain[diff - 1]
+          if (targetBlock && targetBlock.hash === forkBlock.previousHash) {
+            return {
+              row: i,
+              col: targetBlock.number - this.chain[0].number
+            }
+          }
+          else if (!targetBlock) {
+            return {
+              row: -1,
+              col: forkBlock.number - this.chain[0].number - 1,
+            }
+          }
+        }
+      }
+      // The origin block does not exist at all
+      return {
+        row: -1,
+        col: -1,
+      }
+    }
+
+    getXoffset(cell: Cell, fork: BlockType[]){
+      return this.xOffset + (cell.col + 1) * (this.circleR * 2 + this.interCircle)
+    }
+
+    getYoffset(cell: Cell, fork: BlockType[]): { yFinal: number, px: number } {
+      if (!cell || !this.chain[0]) {
+        return {
+          yFinal: 0,
+          px: this.xOffset
+        }
+      }
+      let y = 0
+      let oneCellConflicts = true
+      while (oneCellConflicts) {
+        const taken = fork.map((f, i) => this.matrix[y][cell.col + i])
+        oneCellConflicts = taken.reduce((conflicts, cell) => conflicts || cell, false)
+        if (oneCellConflicts) {
+          y++
+        }
+      }
+      // this.matrix[y].forEach((f, i) => this.matrix[y][i] = true)
+      fork.forEach((f, i) => this.matrix[y][cell.col + i] = true)
+      this.maxRow = Math.max(this.maxRow, y)
+      this.maxCol = Math.max(this.maxCol, cell.col + fork.length - 1)
+      return {
+        yFinal: y,
+        px: this.baseYoffset + (y + 1) * this.lineHeight
+      }
+    }
+
+    get branches() {
+      return [this.chain].concat(this.forks)
+    }
+  }
+
+  interface ForkPoint {
+    x: number
+    y: number
+    yFinal: number
+    blocks: BlockType[]
+  }
+
+  interface Point {
+    x: number
+    y: number
+  }
+
+  interface Line {
+    p1: Point
+    p2: Point
+  }
+
+  interface Cell {
+    row: number
+    col: number
+  }
+
+</script>
diff --git a/src/components/svg/SvgBlock.vue b/src/components/svg/SvgBlock.vue
new file mode 100644
index 0000000..6d741e3
--- /dev/null
+++ b/src/components/svg/SvgBlock.vue
@@ -0,0 +1,47 @@
+<style scoped>
+  circle {
+    fill: transparent;
+    stroke: black;
+  }
+
+  text {
+    font-family: Consolas, "Courier New", Courier, monospace;
+    fill: black;
+    font-size: 10px;
+  }
+
+  .prevHash {
+    font-weight: bold;
+  }
+</style>
+
+<template>
+  <g :class="{ cssClass: true }">
+    <circle :cx="x" :cy="y" :r="r"></circle>
+    <text   :x="x - r + r/4 - 5" :y="y - 3">{{ blockNumber }}</text>
+    <text   :x="x - r + r/4 - 2" :y="y + r/2 - 3">#{{ hash.slice(0, 6) }}</text>
+<!--    <text   :x="x - r + r/4 - 2" :y="y + r/2 + 5"-->
+<!--            class="prevHash" v-if="previousHash">#{{ previousHash && previousHash.slice(0, 6) }}</text>-->
+  </g>
+</template>
+
+<script lang="ts">
+  import Component from 'vue-class-component';
+  import Vue from 'vue';
+  import {Prop} from 'vue-property-decorator';
+
+  @Component({})
+  export default class extends Vue {
+    @Prop(Number) x
+    @Prop(Number) y
+    @Prop(Number) r
+    @Prop(Number) number
+    @Prop(String) hash
+    @Prop(String) previousHash
+    @Prop(String) cssClass
+
+    get blockNumber() {
+      return String(this.number).padStart(7, ' ')
+    }
+  }
+</script>
diff --git a/src/components/svg/SvgChain.vue b/src/components/svg/SvgChain.vue
new file mode 100644
index 0000000..b63330d
--- /dev/null
+++ b/src/components/svg/SvgChain.vue
@@ -0,0 +1,82 @@
+<style scoped>
+
+  line.chain {
+    stroke-width: 2px;
+    stroke: black;
+  }
+  
+</style>
+
+<template>
+  <g>
+    <svg-block
+        v-for="c in circles"
+        :x="c.x"
+        :y="c.y"
+        :r="circleR"
+        :number="c.number"
+        :hash="c.hash"
+        :previousHash="c.previousHash"
+        :key="c.hash"
+    ></svg-block>
+    <line class="chain" v-for="(l, index) in lines" :x1="l.x1" :y1="l.y1" :x2="l.x2" :y2="l.y2" :key="index"></line>
+  </g>
+</template>
+
+<script lang="ts">
+  import Component from 'vue-class-component';
+  import Vue from 'vue';
+  import {Prop} from 'vue-property-decorator';
+  import SvgBlock from '@/components/svg/SvgBlock.vue';
+
+  @Component({
+    components: {SvgBlock}
+  })
+  export default class extends Vue {
+
+    @Prop(Array) chain: Block[]
+    @Prop(Number) xOffset: number
+    @Prop(Number) yOffset: number
+    @Prop(Number) pCircleR: number
+    @Prop(Number) pInterCircle: number
+
+    get circleR() {
+      return this.pCircleR
+    }
+
+    get interCircle() {
+      return this.pInterCircle
+    }
+
+    get circles() {
+      const chain: Block[] = this.chain
+      return chain.map((b, i) => ({
+        x: this.xOffset + i * (2 * this.circleR + this.interCircle),
+        y: this.yPos,
+        number: b.number,
+        hash: b.hash,
+        previousHash: b.previousHash
+      }))
+    }
+
+    get lines() {
+      const offset = 0
+      return this.$props.chain.slice(0, this.$props.chain.length - 1).map((b, i) => ({
+        x1: this.xOffset + i * (2 * this.circleR + this.interCircle) + this.circleR,
+        y1: this.yPos,
+        x2: this.xOffset + i * (2 * this.circleR + this.interCircle) + this.circleR + this.interCircle,
+        y2: this.yPos,
+      }))
+    }
+
+    get yPos() {
+      return this.yOffset
+    }
+  }
+
+  interface Block {
+    number: number
+    hash: string
+    previousHash: string
+  }
+</script>
diff --git a/src/lib/services/webmin.service.ts b/src/lib/services/webmin.service.ts
index 343d803..44b717b 100644
--- a/src/lib/services/webmin.service.ts
+++ b/src/lib/services/webmin.service.ts
@@ -1,6 +1,7 @@
 import gql from 'graphql-tag'
 import ApolloClient from 'apollo-client';
 import {DBBlock, NodeState, WS2PConnectionInfos, WS2PHead} from '../../../common/types'
+import {ChainType} from '../../../back/webmin/graphql/types/ChainType'
 
 export class WebminService {
 
@@ -95,7 +96,6 @@ export class WebminService {
       `,
     })
     .result()
-    console.log(res.data.nodeState)
     return res.data.nodeState
   }
 
@@ -292,4 +292,49 @@ export class WebminService {
     .result()
     return res.data.uid
   }
+
+  async getMainChain(start: number, end: number): Promise<ChainType> {
+    const res = await this.getApollo()
+    .watchQuery<{ getMainChain: ChainType }>({
+      query: gql`
+          query ($start: Int!, $end: Int!){
+              getMainChain(start: $start, end: $end) {
+                  blocks {
+                      number
+                      hash
+                  }
+              }
+          }
+      `,
+      variables: {
+        start,
+        end
+      }
+    })
+    .result()
+    return res.data.getMainChain
+  }
+
+  async getForks(start: number, end: number): Promise<ChainType> {
+    const res = await this.getApollo()
+    .watchQuery<{ getForks: ChainType }>({
+      query: gql`
+          query ($start: Int!, $end: Int!){
+              getForks(start: $start, end: $end)  {
+                  blocks {
+                      number
+                      hash
+                      previousHash
+                  }
+              }
+          }
+      `,
+      variables: {
+        start,
+        end
+      }
+    })
+    .result()
+    return res.data.getForks
+  }
 }
diff --git a/src/lib/utils/blockstamp.ts b/src/lib/utils/blockstamp.ts
new file mode 100644
index 0000000..fb822a7
--- /dev/null
+++ b/src/lib/utils/blockstamp.ts
@@ -0,0 +1,3 @@
+export function blockstamp(b: { number: number, hash: string }) {
+  return [b.number, b.hash].join('-')
+}
\ No newline at end of file
diff --git a/src/views/Home.vue b/src/views/Home.vue
index 3eecfde..ab4746d 100644
--- a/src/views/Home.vue
+++ b/src/views/Home.vue
@@ -10,9 +10,9 @@
     <b-navbar toggleable="lg" class="bg-light">
 
       <b-navbar-brand id="brand">
-        <a v-on:click="reload()">
+        <router-link to="/home">
           <img src="../assets/250×250.png" width="40" height="40" alt="">
-        </a>
+        </router-link>
       </b-navbar-brand>
 
       <b-navbar-toggle target="nav-text-collapse"></b-navbar-toggle>
@@ -21,27 +21,14 @@
       <b-collapse id="nav-text-collapse" is-nav>
 
         <b-navbar-nav class="mr-auto">
-          <b-nav-item><button class="btn"><font-awesome-icon icon="cube"/></button></b-nav-item>
-          <b-nav-item><button class="btn"><font-awesome-icon icon="cog"/></button></b-nav-item>
-        </b-navbar-nav>
-
-        <b-navbar-nav>
-
           <b-nav-item>
-            <b>State = {{ state }}</b>
-          </b-nav-item>
-
-          <b-nav-item>
-            <b>Is started = {{ isStarted }}</b>
-          </b-nav-item>
-
-          <b-nav-item>
-            <b>{{ hello2 }}</b>
+            <router-link to="home/forkview">
+              <button class="btn"><font-awesome-icon icon="cube"/></button>
+            </router-link>
           </b-nav-item>
+        </b-navbar-nav>
 
-          <b-nav-item>
-            {{ hello }}
-          </b-nav-item>
+        <b-navbar-nav>
 
           <b-nav-item>
 
@@ -79,13 +66,10 @@
     hello = 'wait...'
 
     async reload() {
-      console.log('Reload!')
       this.hello = await this.$webmin.hello()
-      // TODO
     }
 
     async created() {
-      console.log('Created Home')
       await this.$store.dispatch('resetState')
     }
 
@@ -106,12 +90,5 @@
       return this.$store.state.lastState == 'STARTED'
     }
 
-    get hello2() {
-      return this.$webmin.hello()
-    }
-
-    get state() {
-      return this.$store.state.lastState
-    }
   }
 </script>
diff --git a/src/views/home/Forkview.vue b/src/views/home/Forkview.vue
new file mode 100644
index 0000000..cbda279
--- /dev/null
+++ b/src/views/home/Forkview.vue
@@ -0,0 +1,116 @@
+<style lang="css" scoped>
+
+  .col-12.graph {
+    overflow: auto !important;
+  }
+</style>
+
+<template>
+
+  <div class="container-fluid">
+    <div class="row">
+      <div class="col-12">
+        <p>
+          <input type="number" v-model.number="start" @input="refreshForks" /> =>
+          <input type="number" v-model.number="end" @input="refreshForks" />
+          <input type="submit" @click="refreshForks" />
+        </p>
+        <b-alert variant="warning" show v-if="end - start > MAX_BLOCKS_ALLOWED">{{ $t('fork.view.too.much', [MAX_BLOCKS_ALLOWED] )}}</b-alert>
+      </div>
+      <div class="col-12 graph">
+        <ForkGraph :chain="chain" :forks="forks"></ForkGraph>
+      </div>
+    </div>
+  </div>
+
+</template>
+
+<script lang="ts">
+  import {Component, Vue} from 'vue-property-decorator';
+  import ForkGraph from '@/components/ForkGraph.vue';
+  import {BlockType} from '../../../back/webmin/graphql/types/BlockType';
+  import {blockstamp} from '@/lib/utils/blockstamp';
+
+  @Component({
+    components: {ForkGraph},
+  })
+  export default class extends Vue {
+
+    MAX_BLOCKS_ALLOWED = 1000
+
+    start: number = 0
+    end: number = 0
+
+    chain: BlockType[] = []
+    forks: BlockType[][] = []
+
+    async beforeMount() {
+      const current = await this.$webmin.current()
+      this.start = current.number - 20
+      this.end = current.number + 10
+      await this.refreshForks()
+    }
+
+    async oneLess() {
+      this.start--
+      await this.refreshForks()
+    }
+
+    async refreshForks() {
+      if (this.end - this.start <= this.MAX_BLOCKS_ALLOWED) {
+        this.chain = (await this.$webmin.getMainChain(this.start, this.end)).blocks
+        this.forks = this.blocks2forks((await this.$webmin.getForks(this.start, this.end)).blocks)
+      }
+    }
+
+    private blocks2forks(blocks: BlockType[]) {
+      const splitters: { [bs: string]: BlockType[] } = {}
+      blocks.forEach(b => {
+        const bs = [b.number - 1, b.previousHash].join('-')
+        if (!splitters[bs]) {
+          splitters[bs] = []
+        }
+        splitters[bs].push(b)
+      })
+
+      const chains: BlockType[][] = []
+
+      function digChain(currentChain: BlockType[], bs: string) {
+        const forks = splitters[bs]
+        if (forks) {
+          currentChain.push(forks[0])
+          digChain(currentChain, blockstamp(forks[0]))
+
+          for (let i = 1; i < forks.length; i++) {
+            const newChain = []
+            // chains.push(newChain)
+            // digChain(newChain, [forks[i].number, forks[i].hash].join('-'))
+          }
+        }
+      }
+
+      const bss = blocks.map(blockstamp)
+
+      Object.keys(splitters)
+        // Find the root blocks, not found in the fork blocks
+        .filter(bs => !bss.includes(bs))
+        // Get the older first
+        .sort((b, a) => parseInt(b) - parseInt(a))
+        .forEach(bs => {
+          const forks = splitters[bs]
+          for (let i = 0; i < forks.length; i++) {
+            chains.push([])
+            digChain(chains[chains.length - 1], bs)
+          }
+        })
+
+      chains.sort((c1, c2) => {
+        if (c1.length !== c2.length) {
+          return c1.length - c2.length
+        }
+        return c1[c1.length - 1].number - c2[c2.length - 1].number
+      })
+      return chains
+    }
+  }
+</script>
diff --git a/src/views/home/Overview.vue b/src/views/home/Overview.vue
index 53e8fe8..a39b557 100644
--- a/src/views/home/Overview.vue
+++ b/src/views/home/Overview.vue
@@ -152,8 +152,16 @@
 
     softwares: string[] = []
 
-    async mounted() {
+    private subscription: ZenObservable.Subscription
 
+    async beforeDestroy() {
+      this.subscription.unsubscribe()
+      console.log('beforeDestroy')
+    }
+
+    async beforeMount() {
+
+      console.log('beforeMount')
       this.current = await this.$webmin.current()
       this.currentBest = { bs: [this.current.number, this.current.hash].join('-'), count: 1 }
       this.updateHeads(await this.$webmin.heads())
@@ -166,7 +174,7 @@
         }
       })
 
-      this.$webmin.newDocuments().subscribe(async v => {
+      this.subscription = this.$webmin.newDocuments().subscribe(async v => {
         const ev = v.data.newDocuments
         ev.blocks.forEach(b => console.log(b))
         this.blocks += ev.blocks.length
diff --git a/src/vue-modules/register-router.ts b/src/vue-modules/register-router.ts
index bd3272d..57f1e3b 100644
--- a/src/vue-modules/register-router.ts
+++ b/src/vue-modules/register-router.ts
@@ -1,10 +1,11 @@
 import Vue from 'vue'
 import Router from 'vue-router'
-import {Constants, RouteNames} from '@/lib/constants'
+import {RouteNames} from '@/lib/constants'
 import Home from '@/views/Home.vue'
 import Loader from '@/views/Loader.vue'
 import Sync from '@/views/Sync.vue'
 import Overview from '@/views/home/Overview.vue'
+import Forkview from '@/views/home/Forkview.vue'
 
 Vue.use(Router)
 
@@ -30,6 +31,10 @@ export default new Router({
         path: '',
         name: 'overview',
         component: Overview
+      },{
+        path: 'forkview',
+        name: 'forkview',
+        component: Forkview
       }]
     },
     {
diff --git a/src/vue-modules/register-store.ts b/src/vue-modules/register-store.ts
index 3668d77..50bddb6 100644
--- a/src/vue-modules/register-store.ts
+++ b/src/vue-modules/register-store.ts
@@ -21,7 +21,6 @@ export default function($webmin: WebminService): Store<DuniterNodeState> {
     },
     actions: {
       async resetState({ commit }) {
-        console.log('Reset state ...')
         commit('setLastState', await $webmin.nodeState())
 
         // switch (await this.lastState) {
diff --git a/tsconfig.json b/tsconfig.json
index a82b7cf..8a50e24 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -29,6 +29,7 @@
     ]
   },
   "include": [
+    "src/*.ts",
     "src/**/*.ts",
     "src/**/*.tsx",
     "src/**/*.vue"
-- 
GitLab