Skip to content
Snippets Groups Projects
Unverified Commit 383198c0 authored by Wolfgang Welz's avatar Wolfgang Welz Committed by GitHub
Browse files

Merge pull request #80 from Evanfeenstra/ui

UI and webauth plugins
parents fda5298d b4805e61
Branches
Tags
No related merge requests found
Showing
with 2403 additions and 1 deletion
...@@ -5,6 +5,7 @@ go 1.13 ...@@ -5,6 +5,7 @@ go 1.13
require ( require (
github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect github.com/AndreasBriese/bbloom v0.0.0-20190825152654-46b345b51c96 // indirect
github.com/dgraph-io/badger v1.6.0 github.com/dgraph-io/badger v1.6.0
github.com/dgrijalva/jwt-go v3.2.0+incompatible
github.com/ethereum/go-ethereum v1.9.3 github.com/ethereum/go-ethereum v1.9.3
github.com/gdamore/tcell v1.2.0 github.com/gdamore/tcell v1.2.0
github.com/go-zeromq/zmq4 v0.5.0 github.com/go-zeromq/zmq4 v0.5.0
......
...@@ -18,6 +18,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c ...@@ -18,6 +18,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ= github.com/dgraph-io/badger v1.5.4/go.mod h1:VZxzAIRPHRVNRKRo6AXrX9BJegn6il06VMTZVJYCIjQ=
github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4= github.com/dgraph-io/badger v1.6.0/go.mod h1:zwt7syl517jmP8s94KqSxTlM6IMsdhYy6psNgSztDR4=
github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM=
github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190323231341-8198c7b169ec/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw= github.com/dgryski/go-farm v0.0.0-20190423205320-6a90982ecee2/go.mod h1:SqUrOPUnsFjfmXRMNPybcSiG0BgUW2AuFH8PAnS2iTw=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
......
...@@ -8,16 +8,18 @@ import ( ...@@ -8,16 +8,18 @@ import (
"github.com/iotaledger/goshimmer/plugins/cli" "github.com/iotaledger/goshimmer/plugins/cli"
"github.com/iotaledger/goshimmer/plugins/dashboard" "github.com/iotaledger/goshimmer/plugins/dashboard"
"github.com/iotaledger/goshimmer/plugins/gossip" "github.com/iotaledger/goshimmer/plugins/gossip"
"github.com/iotaledger/goshimmer/plugins/gossip-on-solidification" gossip_on_solidification "github.com/iotaledger/goshimmer/plugins/gossip-on-solidification"
"github.com/iotaledger/goshimmer/plugins/gracefulshutdown" "github.com/iotaledger/goshimmer/plugins/gracefulshutdown"
"github.com/iotaledger/goshimmer/plugins/metrics" "github.com/iotaledger/goshimmer/plugins/metrics"
"github.com/iotaledger/goshimmer/plugins/statusscreen" "github.com/iotaledger/goshimmer/plugins/statusscreen"
statusscreen_tps "github.com/iotaledger/goshimmer/plugins/statusscreen-tps" statusscreen_tps "github.com/iotaledger/goshimmer/plugins/statusscreen-tps"
"github.com/iotaledger/goshimmer/plugins/tangle" "github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/goshimmer/plugins/tipselection" "github.com/iotaledger/goshimmer/plugins/tipselection"
"github.com/iotaledger/goshimmer/plugins/ui"
"github.com/iotaledger/goshimmer/plugins/webapi" "github.com/iotaledger/goshimmer/plugins/webapi"
webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi-gtta" webapi_gtta "github.com/iotaledger/goshimmer/plugins/webapi-gtta"
webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi-spammer" webapi_spammer "github.com/iotaledger/goshimmer/plugins/webapi-spammer"
"github.com/iotaledger/goshimmer/plugins/webauth"
"github.com/iotaledger/goshimmer/plugins/zeromq" "github.com/iotaledger/goshimmer/plugins/zeromq"
) )
...@@ -42,5 +44,8 @@ func main() { ...@@ -42,5 +44,8 @@ func main() {
webapi.PLUGIN, webapi.PLUGIN,
webapi_gtta.PLUGIN, webapi_gtta.PLUGIN,
webapi_spammer.PLUGIN, webapi_spammer.PLUGIN,
ui.PLUGIN,
webauth.PLUGIN,
) )
} }
This diff is collapsed.
package ui
import (
"time"
"github.com/iotaledger/goshimmer/packages/node"
)
var logHistory = make([]*statusMessage, 0)
type statusMessage struct {
Source string `json:"source"`
Level int `json:"level"`
Message string `json:"message"`
Time time.Time `json:"time"`
}
type resp map[string]interface{}
func storeAndSendStatusMessage(pluginName string, message string, level int) {
msg := &statusMessage{
Source: pluginName,
Level: level,
Message: message,
Time: time.Now(),
}
logHistory = append(logHistory, msg)
ws.send(resp{
"logs": []*statusMessage{msg},
})
}
var uiLogger = &node.Logger{
LogInfo: func(pluginName string, message string) {
storeAndSendStatusMessage(pluginName, message, node.LOG_LEVEL_INFO)
},
LogSuccess: func(pluginName string, message string) {
storeAndSendStatusMessage(pluginName, message, node.LOG_LEVEL_SUCCESS)
},
LogWarning: func(pluginName string, message string) {
storeAndSendStatusMessage(pluginName, message, node.LOG_LEVEL_WARNING)
},
LogFailure: func(pluginName string, message string) {
storeAndSendStatusMessage(pluginName, message, node.LOG_LEVEL_FAILURE)
},
LogDebug: func(pluginName string, message string) {
storeAndSendStatusMessage(pluginName, message, node.LOG_LEVEL_DEBUG)
},
}
package ui
import (
"sync/atomic"
"time"
"github.com/iotaledger/goshimmer/packages/accountability"
"github.com/iotaledger/goshimmer/plugins/autopeering/instances/acceptedneighbors"
"github.com/iotaledger/goshimmer/plugins/autopeering/instances/chosenneighbors"
"github.com/iotaledger/goshimmer/plugins/autopeering/instances/knownpeers"
"github.com/iotaledger/goshimmer/plugins/autopeering/instances/neighborhood"
)
var start = time.Now()
var receivedTpsCounter uint64
var solidTpsCounter uint64
var tpsQueue []uint32
const maxQueueSize int = 3600
type nodeInfo struct {
ID string `json:"id"`
ChosenNeighbors []string `json:"chosenNeighbors"`
AcceptedNeighbors []string `json:"acceptedNeighbors"`
KnownPeersSize int `json:"knownPeers"`
NeighborhoodSize int `json:"neighborhood"`
Uptime uint64 `json:"uptime"`
ReceivedTps uint64 `json:"receivedTps"`
SolidTps uint64 `json:"solidTps"`
}
func gatherInfo() nodeInfo {
// update tps queue
tpsQueue = append(tpsQueue, uint32(receivedTpsCounter))
if len(tpsQueue) > maxQueueSize {
tpsQueue = tpsQueue[1:]
}
// update neighbors
chosenNeighbors := []string{}
acceptedNeighbors := []string{}
for _, peer := range chosenneighbors.INSTANCE.Peers.GetMap() {
chosenNeighbors = append(chosenNeighbors, peer.String())
}
for _, peer := range acceptedneighbors.INSTANCE.Peers.GetMap() {
acceptedNeighbors = append(acceptedNeighbors, peer.String())
}
receivedTps, solidTps := updateTpsCounters()
duration := time.Since(start) / time.Second
info := nodeInfo{
ID: accountability.OwnId().StringIdentifier,
ChosenNeighbors: chosenNeighbors,
AcceptedNeighbors: acceptedNeighbors,
KnownPeersSize: knownpeers.INSTANCE.Peers.Len(),
NeighborhoodSize: neighborhood.INSTANCE.Peers.Len(),
Uptime: uint64(duration),
ReceivedTps: receivedTps,
SolidTps: solidTps,
}
return info
}
func updateTpsCounters() (uint64, uint64) {
receivedTps := atomic.LoadUint64(&receivedTpsCounter)
solidTps := atomic.LoadUint64(&solidTpsCounter)
atomic.StoreUint64(&receivedTpsCounter, 0)
atomic.StoreUint64(&solidTpsCounter, 0)
return receivedTps, solidTps
}
var fs = require('fs');
async function run() {
var content = 'package ui\n\nvar files = map[string]string{\n'
var files = walk('.')
var skip = ['favicon.ico','builder.js']
await asyncForEach(files, async function(file){
var fileName = file.substring(2, file.length)
if(skip.includes(fileName)) return
var data = await readFile(file)
content += addContent(fileName, data)
})
content += '\n}\n'
fs.writeFile('../files.go',content,function(){
console.log('files.go created!')
})
}
function addContent(fileName, data) {
return '\t"'+fileName+'":`' +data +'`,\n'
}
function readFile(file) {
return new Promise(function(resolve,reject){
fs.readFile(file, {encoding: 'utf-8'}, function(err,data){
if (!err) {
resolve(data)
} else {
reject(err)
}
});
})
}
var walk = function(dir) {
var results = [];
var list = fs.readdirSync(dir);
list.forEach(function(file) {
file = dir + '/' + file;
var stat = fs.statSync(file);
if (stat && stat.isDirectory()) {
/* Recurse into a subdirectory */
results = results.concat(walk(file));
} else {
/* Is a file */
results.push(file);
}
});
return results;
}
async function asyncForEach(array, callback) {
for (let index = 0; index < array.length; index++) {
await callback(array[index], index, array);
}
}
run()
\ No newline at end of file
/* mobile */
html {
font-size:12px;
}
/* tablets */
@media only screen and (min-width: 600px) {
html {
font-size:14px;
}
}
/* desktop */
@media only screen and (min-width: 768px) {
html {
font-size:16px;
}
}
body {
opacity: 1;
transition: 0.35s opacity;
}
body.fade {
opacity: 0;
transition: none;
}
/* bulma */
[v-cloak] {
display: none;
}
.section {
padding: 1.8rem 1.5rem;
}
.columns {
margin-bottom: 0 !important;
}
.header{
background:#005050;
padding-bottom: 0;
}
/* main */
.title{
display: flex;
align-items: center;
}
.login{
background:#004965;
border: 1px solid #5e6d6f;
padding:20px;
font-size: 0.9rem;
border-radius: 0.4em;
box-shadow: 0 2px 3px rgba(10, 10, 10, 0.1), 0 0 0 1px rgba(10, 10, 10, 0.1);
}
.login button{
width:100%;
margin-top:12px;
}
.login div {
display: flex;
align-items: center;
font-size:1.2rem;
}
.login div:first-child {
margin-bottom:12px;
}
.login div span {
min-width:6.5rem;
}
.status{
margin-left:54px;
}
.status > span {
margin-left:8px;
}
.tps.input {
width:100px;
}
.tx-toolbar {
display: flex;
justify-content: space-between;
height:50px;
}
.graph {
border: 1px solid #5e6d6f;
overflow: hidden;
}
#graph {
width:100%;
height: 100%;
}
.hovered-node{
position: absolute;
top: 0;
font-size: 0.75rem;
padding: 5px 10px;
width: 100%;
color: #c5c5c5;
}
.chart-container {
height: calc(100% - 50px);
display: flex;
border:1px solid #5e6d6f;
overflow: hidden;
}
/* tables */
.info-list{
background:#004965;
border: 1px solid #5e6d6f;
font-size: 0.9rem;
}
.info-list .list-item{
display: flex;
justify-content: space-between;
white-space: nowrap;
overflow: hidden;
}
.logs-container {
overflow: scroll;
display: flex;
flex-direction: column-reverse;
border:1px solid #5e6d6f;
}
.logs-list {
background:#004965;
table-layout: fixed;
width: 100%;
font-size: 0.9rem;
}
.logs-list th {
background: #00374c;
}
.logs-list td {
font-family: "Inconsolata", "Consolas", "Monaco", monospace;
padding: 0.25em 0.75em;
overflow: auto;
white-space: nowrap;
}
.logs-list td::-webkit-scrollbar { height: 0 !important }
.logs-list td:first-child {
font-weight: bold;
}
.logs-list td:last-child span {
width:35px;
text-align: center;
font-weight: bold;
display: inline-block;
}
.tx-container {
height: 100%;
overflow: scroll;
display: flex;
flex-direction: column-reverse;
border:1px solid #5e6d6f;
}
.tx-list {
background:#004965;
table-layout: fixed;
width: 100%;
font-size: 0.9rem;
}
.tx-list th {
padding-bottom: 3px;
border-top:1px solid #5e6d6f !important;
background: #00374c;
}
.tx-list tbody tr {
cursor: pointer;
}
.tx-list tbody tr:hover {
background: #00374c;
}
.tx-list td {
font-family: "Inconsolata", "Consolas", "Monaco", monospace;
padding: 0.25em 0.75em;
overflow: auto;
white-space: nowrap;
}
.tx-list td::-webkit-scrollbar { height: 0 !important }
.tx-list td:first-child {
font-weight: bold;
}
.tx-list tr.full-tx {
display:block;
width:100%;
}
.tx-list tr.full-tx td {
padding:0;
width:100%;
}
.tx-list tr.full-tx pre:hover {
background:#232929;
}
.no-txs{
height:100%;
width:100%;
display:flex;
align-items: center;
justify-content: center;
color:grey;
font-size: 0.8rem;
}
plugins/ui/src/favicon.ico

16.6 KiB

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="ui/favicon.ico">
<title>Shimmer UI</title>
<link rel="stylesheet" href="https://unpkg.com/bulmaswatch@0.7.5/darkly/bulmaswatch.min.css">
<link rel="stylesheet" href="ui/css/styles.css">
</head>
<body class="fade">
<section id="app">
<section class="section header" ref="header">
<div class="container">
<div class="columns">
<div class="column">
<h1 class="title">
<iota-icon size="42"></iota-icon>
Shimmer
</h1>
<span v-if="loggedIn" class="status">Status:<span class="tag is-light">{{synced}}</span>
</div>
<div v-if="!loggedIn" class="column">
<form class="login" @submit="login">
<div>
<span>Username:</span>
<input class="input" placeholder="Username" name="username">
</div>
<div>
<span>Password:</span>
<input class="input" placeholder="Username" name="password">
</div>
<button type="submit" :disabled="loginError?true:false"
:class="loginError?'button is-danger':'button is-primary'">
{{ loginButtonText }}
</button>
</form>
</div>
<div v-if="loggedIn" class="column">
<div class="list info-list">
<div v-for="(c, i) in infoKeys" class="list-item">
<span>{{ c }}:</span>&nbsp;
<span v-if="infoValues[i]">{{ infoValues[i] }}</span>
</li>
</div>
</div>
</div>
</div>
<div v-if="loggedIn" class="tabs is-boxed">
<ul>
<li v-for="t in tabs" @click="selectTab(t)"
:class="selectedTab===t?'is-active':''">
<a>{{t}}</a>
</li>
</ul>
</div>
</section>
<section class="section" v-if="selectedTab==='Logs'">
<div class="container logs-container" :style="footerContainerStyle()">
<table class="table logs-list">
<thead v-if="logs.length>0"><tr>
<th style="width:75px;">Time</th>
<th>Message</th>
<th style="width:75px;">Status</th>
</tr></thead>
<tbody>
<tr v-for="log in logs">
<td>{{ log.time }}</td>
<td>{{ log.source }}: {{ log.message }}</td>
<td>[<span :style="'color:'+log.color+';'">{{ log.label }}</span>]</td>
</tr>
</tbody>
</table>
</div>
</section>
<section class="section" v-if="selectedTab==='Spammer'">
<div class="container" :style="footerContainerStyle()">
<div class="tx-toolbar">
<div class="field is-grouped">
<p class="control">
<input ref="tpsinput" class="input tps" type="number" v-model="tpsToSpam" placeholder="TPS">
</p>
<p class="control">
<button class="button is-info" @click="startSpam()" :disabled="tpsToSpam<1">
<play-icon size="19"></play-icon>
</button>
</p>
<p class="control">
<button class="button is-primary" @click="stopSpam()" :disabled="info.receivedTps<1">
<stop-icon size="19"></stop-icon>
</button>
</p>
</div>
<!-- <div class="field is-grouped">
<button class="button" @click="clearTxs()" :disabled="txs.length<1">
Clear
</button>
</div> -->
</div>
<div class="chart-container">
<tps-chart ref="tpschart"></tps-chart>
</div>
</div>
</section>
<section class="section" v-if="selectedTab==='Transactions'">
<div class="container transactions" :style="footerContainerStyle()">
<div class="tx-container">
<table v-if="txs.length>0" class="table tx-list">
<thead><tr>
<th style="width:50px;"><iota-icon size="20"></iota-icon></th>
<th style="line-height:21px;">Hash</th>
</tr></thead>
<tbody>
<tr v-for="tx in txs" @click="selectTxHash(tx.hash)" :class="selectedTxHash===tx.hash ? 'full-tx' : ''">
<td v-if="selectedTxHash!==tx.hash">{{ tx.value }}</td>
<td>
<span v-if="selectedTxHash!==tx.hash">{{ tx.hash }}</span>
<pre v-if="selectedTxHash===tx.hash" :style="'width:calc('+(windowWidth-2)+'px - 3rem);'">{{ JSON.stringify(tx,null,2) }}</pre>
</td>
</tr>
</tbody>
</table>
<div v-if="txs.length===0" class="no-txs">No transactions yet</div>
</div>
</div>
</section>
<section class="section" v-if="selectedTab==='Neighbors'">
<div class="container graph" :style="footerContainerStyle()">
<force-graph :neighbors="neighbors" :me="info.id"></force-graph>
</div>
</section>
</section>
<!-- <script src="https://unpkg.com/vue@2.5.9"></script> -->
<script src="https://unpkg.com/vue@2.6.10/dist/vue.min.js"></script>
<script src="https://unpkg.com/dayjs@1.8.15"></script>
<script src="https://unpkg.com/3d-force-graph"></script>
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://code.highcharts.com/stock/highstock.js"></script>
<script src="https://code.highcharts.com/stock/modules/exporting.js"></script>
<script src="https://code.highcharts.com/stock/modules/export-data.js"></script>
<script src="ui/js/initials.js"></script>
<script src="ui/js/utils.js"></script>
<script src="ui/js/icons.js"></script>
<script src="ui/js/forcegraph.js"></script>
<script src="ui/js/tpschart.js"></script>
<script src="ui/js/main.js"></script>
</body>
</html>
\ No newline at end of file
Vue.component('force-graph', {
props:['neighbors', 'me'],
data: function() {
return {
hoveredNode: null
}
},
watch:{
neighbors: function (val, oldVal) {
let updateNeeded
val.forEach(node=> {
if(!oldVal.find(n=> n.id===node.id)) updateNeeded=true
})
oldVal.forEach(node=> {
if(!val.find(n=> n.id===node.id)) updateNeeded=true
})
if (updateNeeded) this.graph.graphData(this.makeGraph())
},
},
methods:{
makeGraph() {
return {
nodes: [{id: this.me}, ...this.neighbors],
links: this.neighbors.filter(id => id)
.map(id => ({ source: id, target: this.me }))
};
}
},
mounted() {
const el = document.getElementById('3d-graph')
const parentStyle = window.getComputedStyle(el.parentNode)
this.graph = ForceGraph3D()(el)
.width(parseInt(parentStyle.width)-2)
.height(parseInt(parentStyle.height)-2)
.enableNodeDrag(false)
.onNodeHover(node => {
this.hoveredNode = node
el.style.cursor = node ? 'pointer' : null
})
.onNodeClick(node => console.log(node))
.nodeColor(node => {
if (node.id===this.me) return 'rgba(0,200,255,1)'
return node.accepted ? 'rgba(150,255,150,1)' : 'rgba(255,255,255,1)'
})
.linkColor('rgba(255,255,255,1)')
.linkOpacity(0.85)
.graphData(this.makeGraph())
},
template: '<div style="height:100%;">'+
'<div id="3d-graph"></div>'+
'<div v-if="hoveredNode" class="hovered-node">'+
'ID: {{hoveredNode.id}} &nbsp;&nbsp; '+
'<span v-if="hoveredNode.address">Address: {{hoveredNode.address}}</span>'+
'</div>'+
'</div>'
})
\ No newline at end of file
Vue.component('iota-icon', {
props:['size'],
template: '<svg :height="size" :width="size" style="margin-right:10px;" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" id="Layer_1" x="0px" y="0px" viewBox="0 0 128 128" xml:space="preserve"><path d="M79.6 24.9c-.1-2.8 2.4-5.4 5.6-5.3 2.8.1 5.1 2.5 5.1 5.4 0 3-2.5 5.3-5.6 5.3-2.8 0-5.1-2.5-5.1-5.4z" fill="#FFF"/><path d="M91 95.4c3 0 5.3 2.3 5.3 5.3s-2.4 5.4-5.4 5.4c-2.9 0-5.3-2.4-5.3-5.4 0-3 2.4-5.3 5.4-5.3z" fill="#FFF"/><path d="M22.4 73.4c-3 0-5.3-2.4-5.3-5.5 0-2.9 2.4-5.2 5.4-5.2 3 0 5.3 2.4 5.3 5.5-.1 2.9-2.5 5.2-5.4 5.2z" fill="#FFF"/><path d="M81.9 39.2c0-2.6 2-4.6 4.6-4.6 2.5 0 4.6 2.1 4.6 4.6 0 2.5-2.1 4.6-4.6 4.6-2.6 0-4.6-2.1-4.6-4.6z" fill="#FFF"/><path d="M33.9 55.1c2.6 0 4.6 2 4.7 4.5 0 2.5-2.1 4.6-4.6 4.6-2.5 0-4.6-2.1-4.6-4.6-.1-2.5 2-4.5 4.5-4.5z" fill="#FFF"/><path d="M98.4 45.4c-2.5 0-4.6-2.1-4.6-4.6 0-2.6 2.1-4.6 4.6-4.6 2.5 0 4.6 2.1 4.6 4.6 0 2.6-2 4.6-4.6 4.6z" fill="#FFF"/><path d="M77.9 99.5c-2.5 0-4.6-2.1-4.6-4.6 0-2.5 2-4.5 4.6-4.6 2.5 0 4.6 2.1 4.6 4.6 0 2.5-2.1 4.6-4.6 4.6z" fill="#FFF"/><path d="M33.9 48.5c0 2.5-2.1 4.6-4.6 4.6-2.5 0-4.5-2.1-4.5-4.6 0-2.5 2.1-4.6 4.6-4.6 2.5-.1 4.6 2 4.5 4.6z" fill="#FFF"/><path d="M70.4 109c-2.5 0-4.5-2-4.5-4.6 0-2.5 2-4.5 4.6-4.6 2.5 0 4.6 2.1 4.6 4.6-.1 2.6-2.1 4.6-4.7 4.6z" fill="#FFF"/><path d="M56.9 97.1c0-2.2 1.7-3.9 3.9-3.9s3.9 1.8 3.9 4-1.8 3.9-3.9 3.9c-2.2 0-3.9-1.8-3.9-4z" fill="#FFF"/><path d="M100.9 52.9c0 2.2-1.8 3.9-3.9 3.9-2.1 0-3.9-1.8-3.9-3.9 0-2.2 1.8-4 3.9-3.9 2.1 0 3.9 1.7 3.9 3.9z" fill="#FFF"/><path d="M44.4 43.7c0 2.2-1.7 3.9-3.9 3.9s-3.9-1.7-3.9-4c0-2.2 1.7-3.9 3.9-3.9 2.3.1 4 1.8 3.9 4z" fill="#FFF"/><path d="M49 54.9c0 2.2-1.7 3.9-3.9 3.9s-3.9-1.7-3.9-3.9 1.7-4 3.9-3.9c2.2 0 3.9 1.7 3.9 3.9z" fill="#FFF"/><path d="M35 33.5c0-2.2 1.8-3.9 3.9-3.9 2.2 0 3.9 1.8 3.9 3.9 0 2.2-1.7 3.9-3.9 3.9S35 35.7 35 33.5z" fill="#FFF"/><path d="M81.1 51.3c0-2.2 1.7-3.9 3.9-3.9s4 1.8 3.9 4c0 2.2-1.8 3.9-3.9 3.9-2.2-.2-3.9-1.9-3.9-4z" fill="#FFF"/><path d="M68.2 83.7c2.1 0 3.9 1.8 3.9 3.9 0 2.2-1.8 3.9-4 3.9s-3.9-1.7-3.9-3.9c.1-2.2 1.9-3.9 4-3.9z" fill="#FFF"/><path d="M56.7 103.6c0 2.2-1.7 3.9-3.9 3.9s-3.9-1.7-3.9-3.9 1.8-4 3.9-3.9c2.2 0 3.9 1.7 3.9 3.9z" fill="#FFF"/><path d="M106.5 60.5c-2.1 0-3.8-1.8-3.9-3.9 0-2.2 1.8-3.9 4-3.9 2.1 0 3.9 1.8 3.9 3.9 0 2.2-1.8 3.9-4 3.9z" fill="#FFF"/><path d="M57.5 89.3c0 1.9-1.5 3.4-3.4 3.3-1.9 0-3.4-1.5-3.4-3.3 0-1.9 1.5-3.4 3.4-3.4s3.4 1.5 3.4 3.4z" fill="#FFF"/><path d="M50.7 38.5c1.9 0 3.3 1.5 3.3 3.3 0 1.8-1.5 3.4-3.3 3.4-1.8 0-3.4-1.5-3.4-3.4.1-1.8 1.6-3.3 3.4-3.3z" fill="#FFF"/><path d="M58.2 79.7c0-1.9 1.4-3.4 3.3-3.4s3.4 1.5 3.4 3.3c0 1.9-1.5 3.3-3.3 3.3-1.9.1-3.4-1.3-3.4-3.2z" fill="#FFF"/><path d="M46.2 99.1c-1.9 0-3.4-1.4-3.4-3.3s1.5-3.3 3.3-3.4c1.9 0 3.4 1.5 3.4 3.4 0 1.8-1.5 3.3-3.3 3.3z" fill="#FFF"/><path d="M84.7 61c0 1.8-1.4 3.3-3.3 3.3s-3.4-1.5-3.3-3.4c0-1.9 1.5-3.3 3.3-3.3 1.9 0 3.3 1.5 3.3 3.4z" fill="#FFF"/><path d="M49.1 35c-1.9 0-3.4-1.4-3.4-3.3s1.5-3.4 3.4-3.4c1.8 0 3.3 1.5 3.4 3.4 0 1.8-1.5 3.3-3.4 3.3z" fill="#FFF"/><path d="M93.4 66c-1.9 0-3.3-1.5-3.3-3.3 0-1.8 1.5-3.3 3.4-3.3s3.3 1.5 3.3 3.4c0 1.8-1.5 3.2-3.4 3.2z" fill="#FFF"/><path d="M55.2 56.4c-1.8 0-3.3-1.5-3.3-3.4 0-1.8 1.6-3.3 3.4-3.3 1.9 0 3.3 1.5 3.3 3.4s-1.5 3.3-3.4 3.3z" fill="#FFF"/><path d="M103 69.6c-1.9-.1-3.3-1.6-3.3-3.5.1-1.8 1.6-3.3 3.4-3.2 1.9.1 3.4 1.6 3.3 3.6-.1 1.8-1.6 3.2-3.4 3.1z" fill="#FFF"/><path d="M64 56.4c-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.8 2.9-2.8 1.6 0 2.9 1.3 2.8 2.9.1 1.6-1.1 2.8-2.8 2.8z" fill="#FFF"/><path d="M95.4 73.7c0-1.6 1.2-2.9 2.8-2.9 1.6 0 2.9 1.2 2.9 2.9 0 1.6-1.4 3-2.9 3-1.5-.1-2.8-1.4-2.8-3z" fill="#FFF"/><path d="M60.7 32.2c0 1.6-1.2 2.8-2.9 2.8-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 3-2.8 1.6 0 2.8 1.3 2.8 2.9z" fill="#FFF"/><path d="M60.4 71.9c0 1.6-1.2 2.8-2.8 2.9-1.7 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9 1.6 0 2.8 1.3 2.8 2.9z" fill="#FFF"/><path d="M50.2 84.3c-1.6 0-2.9-1.2-2.9-2.8 0-1.6 1.3-2.9 2.9-2.9 1.5 0 2.8 1.3 2.8 2.8 0 1.6-1.2 2.9-2.8 2.9z" fill="#FFF"/><path d="M91.5 70c0 1.6-1.2 2.9-2.9 2.9-1.5 0-2.9-1.4-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9 1.7 0 2.9 1.3 2.9 2.9z" fill="#FFF"/><path d="M76.7 65.5c1.7 0 2.8 1.2 2.9 2.8 0 1.6-1.2 2.9-2.9 2.9-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.2-2.8 2.9-2.8z" fill="#FFF"/><path d="M45 87.9c0 1.6-1.3 2.9-2.9 2.9-1.6 0-2.9-1.3-2.9-2.9 0-1.6 1.3-2.9 2.9-2.9 1.6 0 2.9 1.3 2.9 2.9z" fill="#FFF"/><path d="M59.5 45.2c-1.6 0-2.9-1.3-2.9-2.9 0-1.5 1.3-2.9 2.9-2.9 1.5 0 2.9 1.3 2.9 2.9 0 1.6-1.3 2.9-2.9 2.9z" fill="#FFF"/><path d="M85.8 75.1c0 1.4-1.1 2.5-2.5 2.5s-2.4-1.1-2.4-2.5 1.1-2.5 2.4-2.5c1.3.1 2.4 1.2 2.5 2.5z" fill="#FFF"/><path d="M58.2 64.6c0 1.4-1.1 2.5-2.4 2.5-1.3 0-2.5-1.2-2.5-2.5s1.2-2.5 2.5-2.5c1.3.1 2.4 1.2 2.4 2.5z" fill="#FFF"/><path d="M40.4 78.2c1.4 0 2.5 1.1 2.4 2.5 0 1.3-1.2 2.5-2.5 2.5-1.4 0-2.5-1.2-2.5-2.6.1-1.4 1.2-2.4 2.6-2.4z" fill="#FFF"/><path d="M71.2 58c-1.4 0-2.5-1-2.5-2.4s1-2.5 2.5-2.5c1.4 0 2.5 1.1 2.5 2.4 0 1.5-1.1 2.5-2.5 2.5z" fill="#FFF"/><path d="M66.7 41.9c1.4 0 2.4 1.1 2.4 2.5s-1.1 2.5-2.4 2.5c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.5 2.5-2.5z" fill="#FFF"/><path d="M50.7 74.2c0 1.4-1.1 2.5-2.4 2.5-1.3 0-2.5-1.2-2.5-2.6 0-1.4 1.1-2.4 2.5-2.4s2.4 1.1 2.4 2.5z" fill="#FFF"/><path d="M71.3 71.1c1.4 0 2.4 1.1 2.4 2.5S72.6 76 71.3 76c-1.4 0-2.5-1.1-2.5-2.5.1-1.4 1.1-2.4 2.5-2.4z" fill="#FFF"/><path d="M95.3 78.8c0 1.4-1 2.5-2.5 2.5-1.4 0-2.4-1.1-2.4-2.5 0-1.3 1.1-2.5 2.4-2.5 1.4 0 2.5 1.1 2.5 2.5z" fill="#FFF"/><path d="M65.1 31.8c1.4 0 2.4 1 2.4 2.4s-1.1 2.5-2.4 2.5c-1.4 0-2.5-1.1-2.5-2.5s1.1-2.4 2.5-2.4z" fill="#FFF"/><path d="M72.7 37.3c0 1.2-1 2.1-2.2 2.1-1.2 0-2.1-1-2.1-2.1 0-1.2.9-2.1 2.1-2.1 1.3 0 2.2.9 2.2 2.1z" fill="#FFF"/><path d="M38.1 74.2c0-1.2.9-2 2.1-2 1.2 0 2.2 1 2.1 2.2 0 1.1-1 2.1-2.1 2.1-1.2 0-2.1-1-2.1-2.3z" fill="#FFF"/><path d="M89.6 82.1c0 1.2-.9 2.2-2.1 2.2-1.2 0-2.2-1-2.2-2.1 0-1.2 1-2.1 2.2-2.1 1.2-.1 2.1.8 2.1 2z" fill="#FFF"/><path d="M50.3 67.9c0 1.2-1 2.1-2.1 2.1-1.2 0-2.1-1-2.1-2.2 0-1.1 1-2.1 2.1-2.1 1.2 0 2.1 1 2.1 2.2z" fill="#FFF"/><path d="M72.2 49.5c-1.2 0-2.1-.9-2.1-2.1 0-1.2.9-2.1 2.1-2.1 1.2 0 2.1.9 2.1 2.1 0 1.3-.9 2.1-2.1 2.1z" fill="#FFF"/><path d="M77.9 76.3c1.2 0 2.1.9 2.1 2.1 0 1.2-1 2.2-2.2 2.2-1.1 0-2-1-2-2.1 0-1.3.9-2.2 2.1-2.2z" fill="#FFF"/><path d="M43.1 69.1c0 1-.8 1.8-1.8 1.8s-1.9-.9-1.9-1.9c0-1 .9-1.8 1.9-1.8 1.1.1 1.8.8 1.8 1.9z" fill="#FFF"/><path d="M84.2 83.8c0 1-.8 1.8-1.8 1.8s-1.8-.9-1.8-1.8c0-1 .8-1.8 1.9-1.8.9 0 1.7.8 1.7 1.8z" fill="#FFF"/><path d="M74.5 39.1c1.1 0 1.9.7 1.9 1.8 0 1-.8 1.8-1.8 1.8s-1.8-.7-1.8-1.8c0-1 .7-1.8 1.7-1.8z" fill="#FFF"/></svg>'
})
Vue.component('play-circle-icon', {
props:['size'],
template: '<svg :height="size" :width="size" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M256 464c114.875 0 208-93.125 208-208S370.875 48 256 48 48 141.125 48 256s93.125 208 208 208zm-32-112V160l96 96-96 96z"/></svg>'
})
Vue.component('play-icon', {
props:['size'],
template: '<svg :height="size" :width="size" fill="#FFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M96 52v408l320-204L96 52z"/></svg>'
})
Vue.component('stop-icon', {
props:['size'],
template: '<svg :height="size" :width="size" fill="#FFF" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M405.333 64H106.667C83.198 64 64 83.198 64 106.667v298.666C64 428.802 83.198 448 106.667 448h298.666C428.802 448 448 428.802 448 405.333V106.667C448 83.198 428.802 64 405.333 64z"/></svg>'
})
Vue.component('empty-icon', {
template: '<svg></svg>'
})
\ No newline at end of file
var initialData = {
loggedIn: false,
connected: false,
tabs: ['Logs', 'Spammer', 'Transactions', 'Neighbors'],
selectedTab: '',
logs: [],
tpsToSpam: 1,
receivedTps:0,
txs: [],
info: {},
selectedTxHash:null,
tps:0,
loginError:'',
}
new Vue({
el: '#app',
created() {
this.init()
window.addEventListener('resize', (e)=>{
debounce(this.setHeaderSize, 300)
})
},
data: initialData,
methods: {
async login(e){
e.preventDefault()
const formData = new FormData(e.target);
const pass = formData.get('password')
const user = formData.get('username')
try{
const r = await api.get('login?username='+user+'&password='+pass)
if(r.token){
this.init()
}
}catch(e){
this.loginError = 'Not Authorized'
setTimeout(()=>this.loginError='',2000)
}
},
async init() {
try{
const r = await api.get('loghistory')
this.updateLogs(r)
} catch(e) {
this.show()
return
}
this.show()
this.loggedIn = true
this.selectedTab = 'Logs'
this.ws = new WebSocket(
api.addToken('ws://'+window.location.host+'/ws')
)
this.ws.onopen = (e) => { this.connected=true }
this.ws.onclose = (e) => { this.connected=false }
this.ws.onerror = (e) => { this.connected=false }
this.ws.onmessage = (e) => { this.receive(e.data) }
},
show() {
document.body.className = '';
setTimeout(()=>this.setHeaderSize(),15)
},
footerContainerStyle() {
const size = Math.max(window.innerHeight-this.headerSize, 300)
return 'height:calc('+(size-1)+'px - 3.6rem);'
},
startSpam() {
console.log('start spam', this.tpsToSpam)
api.get('spammer?cmd=start&tps='+this.tpsToSpam)
},
stopSpam() {
console.log('stop spam')
api.get('spammer?cmd=stop')
},
selectTab(tab) {
if(tab==='Spammer'){
setTimeout(()=> this.$refs.tpsinput.focus(), 1)
}
this.selectedTab=tab
},
selectTxHash(hash) {
if(hash===this.selectedTxHash){
this.selectedTxHash = null
} else {
this.selectedTxHash = hash
}
},
receive(data) {
let j
try {
j = JSON.parse(data)
} catch(err){}
this.parseResponse(j)
},
send(data) {
this.ws.send(JSON.stringify(data))
},
parseResponse(r) {
if(r.info){ // node info
this.info = r.info
if(this.$refs.tpschart){
this.$refs.tpschart.addPoint(r.info.receivedTps)
}
}
if(r.txs) {
this.updateTxs(r.txs)
}
if(r.logs) {
this.updateLogs(r.logs)
}
},
updateLogs(newLogs){
this.logs = this.logs.concat(
newLogs.map(j=>{
return {
message:j.message,
source:j.source,
label:logLevels[j.level].label,
color:logLevels[j.level].color,
time: dayjs(j.time).format('hh:mm:ss')}
})
)
},
updateTxs(tx) {
const txs = this.txs.concat(tx).reverse()
this.txs = txs.splice(0,1000).reverse()
},
clearTxs() {
this.txs = []
},
setHeaderSize() {
const h = window.getComputedStyle(this.$refs.header).height
this.headerSize = parseInt(h)
this.windowWidth = window.innerWidth
},
},
computed: {
synced() { return this.connected ? 'Synced':'......' },
infoKeys() {
return ['TPS', 'Node ID', 'Neighbors', 'Peers', 'Uptime']
},
loginButtonText() {
return this.loginError || 'Login'
},
infoValues() {
const i = this.info
return i.id ? [
i.receivedTps+' received / '+i.solidTps+ 'new',
i.id,
i.chosenNeighbors.length+' chosen / '+i.acceptedNeighbors.length+' accepted',
i.knownPeers+' total / '+i.neighborhood+' neighborhood',
uptimeConverter(i.uptime)
] : Array(5).fill('...')
},
neighbors() {
if(this.info.chosenNeighbors && this.info.acceptedNeighbors) {
const cn = this.info.chosenNeighbors.map(s=>{
const a = s.split(' / ')
return {id: a[1]||'', address: a[0], accepted: false}
})
const an = this.info.acceptedNeighbors.map(s=>{
const a = s.split(' / ')
return {id: a[1]||'', address: a[0], accepted: true}
})
return cn.concat(an)
}
return []
}
},
})
\ No newline at end of file
Vue.component('tps-chart', {
props: ['tps'],
watch:{
tps: function (val, oldVal) {
console.log(val)
}
},
methods: {
addPoint(value) {
var time = Date.now() - 1000;
this.chart.series[0].addPoint([time, value], true);
},
},
async created() {
const tpsqueue = await api.get('tpsqueue')
var time = Date.now() - 1000 * (tpsqueue.length + 1);
for (let i = 0; i < tpsqueue.length; i++) {
this.chart.series[0].addPoint([time += 1000, tpsqueue[i]], false);
}
this.chart.redraw();
},
mounted() {
Highcharts.createElement('link', {
href: 'https://fonts.googleapis.com/css?family=Unica+One',
rel: 'stylesheet',
type: 'text/css'
}, null, document.getElementsByTagName('head')[0]);
Highcharts.theme = highchartsTheme
Highcharts.setOptions(Highcharts.theme);
// Start Here
var time = Date.now() - 3000;
data = [[time - 5000, 0]];
var tzoffset = new Date().getTimezoneOffset();
this.chart = Highcharts.stockChart('chart', {
rangeSelector: {
selected: 1
},
title: {
text: 'Transactions per second'
},
time: {
timezoneOffset: tzoffset
},
rangeSelector: {
buttons: [
{
type: 'minute',
count: 5,
text: '5m'
}, {
type: 'minute',
count: 15,
text: '15m'
}, {
type: 'minute',
count: 30,
text: '30m'
}, {
type: 'hour',
count: 60,
text: '1h'
}],
inputEnabled: false
},
series: [{
name: 'Transactions per second',
data: data,
type: 'areaspline',
threshold: null,
tooltip: {
valueDecimals: 0
},
fillColor: {
linearGradient: {
x1: 0,
y1: 0,
x2: 0,
y2: 1
},
stops: [
[0, Highcharts.getOptions().colors[0]],
[1, Highcharts.Color(Highcharts.getOptions().colors[0]).setOpacity(0).get('rgba')]
]
}
}]
});
},
template: '<div id="chart" style="height:100%;min-width:100%"></div>'
})
var highchartsTheme = {
colors: ['#2b908f', '#90ee7e', '#f45b5b', '#7798BF', '#aaeeee', '#ff0066',
'#eeaaee', '#55BF3B', '#DF5353', '#7798BF', '#aaeeee'],
chart: {
backgroundColor: {
linearGradient: { x1: 0, y1: 0, x2: 1, y2: 1 },
stops: [
[0, '#2a2a2b'],
[1, '#3e3e40']
]
},
// style: {
// fontFamily: 'sans-serif'
// },
plotBorderColor: '#606063'
},
title: {
style: {
color: '#E0E0E3',
// textTransform: 'uppercase',
fontSize: '20px'
}
},
subtitle: {
style: {
color: '#E0E0E3',
// textTransform: 'uppercase'
}
},
xAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
title: {
style: {
color: '#A0A0A3'
}
}
},
yAxis: {
gridLineColor: '#707073',
labels: {
style: {
color: '#E0E0E3'
}
},
lineColor: '#707073',
minorGridLineColor: '#505053',
tickColor: '#707073',
tickWidth: 1,
title: {
style: {
color: '#A0A0A3'
}
}
},
tooltip: {
backgroundColor: 'rgba(0, 0, 0, 0.85)',
style: {
color: '#F0F0F0'
}
},
plotOptions: {
series: {
dataLabels: {
color: '#B0B0B3'
},
marker: {
lineColor: '#333'
}
},
boxplot: {
fillColor: '#505053'
},
candlestick: {
lineColor: 'white'
},
errorbar: {
color: 'white'
}
},
legend: {
itemStyle: {
color: '#E0E0E3'
},
itemHoverStyle: {
color: '#FFF'
},
itemHiddenStyle: {
color: '#606063'
}
},
credits: {
style: {
color: '#666'
}
},
labels: {
style: {
color: '#707073'
}
},
drilldown: {
activeAxisLabelStyle: {
color: '#F0F0F3'
},
activeDataLabelStyle: {
color: '#F0F0F3'
}
},
navigation: {
buttonOptions: {
symbolStroke: '#DDDDDD',
theme: {
fill: '#505053'
}
}
},
// scroll charts
rangeSelector: {
buttonTheme: {
fill: '#505053',
stroke: '#000000',
style: {
color: '#CCC'
},
states: {
hover: {
fill: '#707073',
stroke: '#000000',
style: {
color: 'white'
}
},
select: {
fill: '#000003',
stroke: '#000000',
style: {
color: 'white'
}
}
}
},
inputBoxBorderColor: '#505053',
inputStyle: {
backgroundColor: '#333',
color: 'silver'
},
labelStyle: {
color: 'silver'
}
},
navigator: {
handles: {
backgroundColor: '#666',
borderColor: '#AAA'
},
outlineColor: '#CCC',
maskFill: 'rgba(255,255,255,0.1)',
series: {
color: '#7798BF',
lineColor: '#A6C7ED'
},
xAxis: {
gridLineColor: '#505053'
}
},
scrollbar: {
barBackgroundColor: '#808083',
barBorderColor: '#808083',
buttonArrowColor: '#CCC',
buttonBackgroundColor: '#606063',
buttonBorderColor: '#606063',
rifleColor: '#FFF',
trackBackgroundColor: '#404043',
trackBorderColor: '#404043'
},
// special colors for some of the
legendBackgroundColor: 'rgba(0, 0, 0, 0.5)',
background2: '#505053',
dataLabelsColor: '#B0B0B3',
textColor: '#C0C0C0',
contrastTextColor: '#F0F0F3',
maskColor: 'rgba(255,255,255,0.3)'
}
\ No newline at end of file
function uptimeConverter(seconds) {
var s = ''
var hrs = Math.floor(seconds / 3600);
if (hrs) s += hrs+'h '
seconds -= hrs*3600;
var mnts = Math.floor(seconds / 60);
if (mnts) s += mnts+'m '
seconds -= mnts*60;
s += seconds + 's'
return s
}
// indexed by number
const logLevels = [{
color: '#e74c3c',
name: 'LOG_LEVEL_FAILURE',
label: 'FAIL',
},{
color: '#f1b70e',
name: 'LOG_LEVEL_WARNING',
label: 'WARN',
},{
color: '#2ecc71',
name: 'LOG_LEVEL_SUCCESS',
label: 'OK',
},{
color: '#209cee',
name: 'LOG_LEVEL_INFO',
label: 'INFO',
},{
color: '#375a7f',
name: 'LOG_LEVEL_DEBUG',
label: 'NOTE',
}]
const api = {
get: async function(u) {
const url = this.trim('/'+u)
try{
const r = await fetch(this.addToken(url))
const j = await r.json()
if (!(r.status >= 200 && r.status < 300)) {
throw new Error(j)
}
if(url.startsWith('login') && j.token) {
localStorage.setItem('token', j.token)
await sleep(1)
}
return j
} catch(e) {
throw e
}
},
post: async function(u, data){
const url = this.trim('/'+u)
try{
const r = await fetch(this.addToken(url), {
method: 'POST',
headers: {'Content-Type': 'application/json'},
data: JSON.stringify(data)
})
const j = await r.json()
if (!(r.status >= 200 && r.status < 300)) {
throw new Error(j)
}
return j
} catch(e) {
throw e
}
},
trim: function(s) {
return s.replace(/^\//, '');
},
addToken: function(s) {
const token = localStorage.getItem('token')
if (!token) return s
const char = s.includes('?') ? '&' : '?'
return s + char + 'token=' + token
}
}
let inDebounce
function debounce(func, delay) {
const context = this
const args = arguments
clearTimeout(inDebounce)
inDebounce = setTimeout(() => func.apply(context, args), delay)
}
function sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
\ No newline at end of file
package ui
import (
"strings"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/iota.go/transaction"
)
// cleared every second
var transactions []transaction.Transaction
var emptyTag = strings.Repeat("9", 27)
func logTransactions() interface{} {
a := transactions
transactions = make([]transaction.Transaction, 0)
return a
}
func saveTx(tx *value_transaction.ValueTransaction) {
transactions = append(transactions, transaction.Transaction{
Hash: tx.MetaTransaction.GetHash(),
Address: tx.GetAddress(),
Value: tx.GetValue(),
Timestamp: uint64(tx.GetTimestamp()),
TrunkTransaction: tx.MetaTransaction.GetTrunkTransactionHash(),
BranchTransaction: tx.MetaTransaction.GetBranchTransactionHash(),
Tag: emptyTag,
})
}
package ui
import (
"net/http"
"strings"
"sync/atomic"
"time"
"github.com/iotaledger/goshimmer/packages/daemon"
"github.com/iotaledger/goshimmer/packages/events"
"github.com/iotaledger/goshimmer/packages/model/meta_transaction"
"github.com/iotaledger/goshimmer/packages/model/value_transaction"
"github.com/iotaledger/goshimmer/packages/node"
"github.com/iotaledger/goshimmer/plugins/gossip"
"github.com/iotaledger/goshimmer/plugins/tangle"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/labstack/echo"
)
func configure(plugin *node.Plugin) {
//webapi.Server.Static("ui", "plugins/ui/src")
webapi.AddEndpoint("ui", func(c echo.Context) error {
return c.HTML(http.StatusOK, files["index.html"])
})
webapi.AddEndpoint("ui/**", staticFileServer)
webapi.AddEndpoint("ws", upgrader)
webapi.AddEndpoint("loghistory", func(c echo.Context) error {
return c.JSON(http.StatusOK, logHistory)
})
webapi.AddEndpoint("tpsqueue", func(c echo.Context) error {
return c.JSON(http.StatusOK, tpsQueue)
})
gossip.Events.ReceiveTransaction.Attach(events.NewClosure(func(_ *meta_transaction.MetaTransaction) {
atomic.AddUint64(&receivedTpsCounter, 1)
}))
tangle.Events.TransactionSolid.Attach(events.NewClosure(func(_ *value_transaction.ValueTransaction) {
atomic.AddUint64(&solidTpsCounter, 1)
}))
tangle.Events.TransactionStored.Attach(events.NewClosure(func(tx *value_transaction.ValueTransaction) {
go func() {
saveTx(tx)
}()
}))
node.DEFAULT_LOGGER.SetEnabled(false)
uiLogger.SetEnabled(true)
plugin.Node.AddLogger(uiLogger)
daemon.Events.Shutdown.Attach(events.NewClosure(func() {
node.DEFAULT_LOGGER.SetEnabled(true)
}))
}
func staticFileServer(c echo.Context) error {
url := c.Request().URL.String()
path := url[4:] // trim off "/ui/"
res := c.Response()
header := res.Header()
if strings.HasPrefix(path, "css") {
header.Set(echo.HeaderContentType, "text/css")
}
if strings.HasPrefix(path, "js") {
header.Set(echo.HeaderContentType, "application/javascript")
}
return c.String(http.StatusOK, files[path])
}
func run(plugin *node.Plugin) {
daemon.BackgroundWorker("UI Refresher", func() {
for {
select {
case <-daemon.ShutdownSignal:
return
case <-time.After(1 * time.Second):
ws.send(resp{
"info": gatherInfo(),
"txs": logTransactions(),
})
}
}
})
}
// PLUGIN plugs the UI into the main program
var PLUGIN = node.NewPlugin("UI", node.Disabled, configure, run)
package ui
import (
"encoding/json"
"fmt"
"github.com/labstack/echo"
"golang.org/x/net/websocket"
)
type socket struct {
conn *websocket.Conn
}
var ws socket
func (sock socket) send(msg interface{}) {
payload, err := json.Marshal(msg)
if err == nil && sock.conn != nil {
fmt.Fprint(sock.conn, string(payload))
}
}
func upgrader(c echo.Context) error {
websocket.Handler(func(conn *websocket.Conn) {
ws.conn = conn
defer conn.Close()
for {
msg := ""
err := websocket.Message.Receive(conn, &msg)
if err != nil {
//c.Logger().Error(err)
break
}
fmt.Printf("%s\n", msg)
}
}).ServeHTTP(c.Response(), c.Request())
return nil
}
package webauth
import (
"net/http"
"os"
"strings"
"time"
"github.com/iotaledger/goshimmer/packages/daemon"
"github.com/iotaledger/goshimmer/packages/node"
"github.com/iotaledger/goshimmer/plugins/webapi"
"github.com/labstack/echo"
"github.com/labstack/echo/middleware"
"github.com/dgrijalva/jwt-go"
)
var secret = "secret"
func configure(plugin *node.Plugin) {
jwtKey := os.Getenv("JWT_KEY")
if jwtKey != "" {
secret = jwtKey
}
webapi.Server.Use(middleware.JWTWithConfig(middleware.JWTConfig{
SigningKey: []byte(secret),
TokenLookup: "query:token",
Skipper: func(c echo.Context) bool {
// if strings.HasPrefix(c.Request().Host, "localhost") {
// return true
// }
if strings.HasPrefix(c.Path(), "/ui") || c.Path() == "/login" {
return true
}
return false
},
}))
}
func run(plugin *node.Plugin) {
daemon.BackgroundWorker("webauth", func() {
webapi.AddEndpoint("login", func(c echo.Context) error {
username := c.FormValue("username")
password := c.FormValue("password")
uiUser := os.Getenv("UI_USER")
uiPass := os.Getenv("UI_PASS")
// Throws unauthorized error
if username != uiUser || password != uiPass {
return echo.ErrUnauthorized
}
token := jwt.New(jwt.SigningMethodHS256)
claims := token.Claims.(jwt.MapClaims)
claims["name"] = username
claims["exp"] = time.Now().Add(time.Hour * 24 * 7).Unix()
t, err := token.SignedString([]byte(secret))
if err != nil {
return err
}
return c.JSON(http.StatusOK, map[string]string{
"token": t,
})
})
})
}
// PLUGIN plugs the UI into the main program
var PLUGIN = node.NewPlugin("webauth", node.Disabled, configure, run)
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment