-
Luca Moser authoredLuca Moser authored
files.go 30.58 KiB
package ui
var files = map[string]string{
"css/styles.css": `/* 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;
}
`,
"index.html": `<!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>GoShimmer 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>
GoShimmer
</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>
<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>`,
"js/forcegraph.js": `
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}} '+
'<span v-if="hoveredNode.address">Address: {{hoveredNode.address}}</span>'+
'</div>'+
'</div>'
})`,
"js/icons.js": `
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>'
})`,
"js/initials.js": `var initialData = {
loggedIn: false,
connected: false,
tabs: ['Logs', 'Spammer', 'Transactions', 'Neighbors'],
selectedTab: '',
logs: [],
tpsToSpam: 1,
receivedTps:0,
txs: [],
info: {},
selectedTxHash:null,
tps:0,
loginError:'',
}
`,
"js/main.js": `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'
let wsProtocol = 'wss:'
if (location.protocol != 'https:') {
wsProtocol = 'ws:'
}
this.ws = new WebSocket(
api.addToken(wsProtocol+'//'+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 []
}
},
})`,
"js/tpschart.js": `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)'
}`,
"js/utils.js": `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));
}`,
}