Browse Source

new-web

master
yuriy0803 3 years ago
parent
commit
e1194ac04c
  1. 16
      new-web/.babelrc
  2. 13
      new-web/.editorconfig
  3. 19
      new-web/.eslintrc.js
  4. 17
      new-web/.github/dependabot.yml
  5. 5
      new-web/.github/semantic.yml
  6. 50
      new-web/.github/workflows/ci.yml
  7. 93
      new-web/.gitignore
  8. 1
      new-web/.husky/.gitignore
  9. 4
      new-web/.husky/pre-commit
  10. 4
      new-web/.prettierrc
  11. 61
      new-web/README.md
  12. 4
      new-web/assets/variables.scss
  13. 122
      new-web/components/ExplorerLink.vue
  14. 134
      new-web/components/tables/Blocks.vue
  15. 126
      new-web/components/tables/Payments.vue
  16. 9
      new-web/content/help/miners/classic/gminer.md
  17. 25
      new-web/content/help/miners/classic/lolminer.md
  18. 15
      new-web/content/help/miners/classic/nanominer.md
  19. 9
      new-web/content/help/miners/classic/nbminer.md
  20. 9
      new-web/content/help/miners/classic/srbminer.md
  21. 9
      new-web/content/help/miners/classic/teamred.md
  22. 9
      new-web/content/help/miners/classic/trex.md
  23. 9
      new-web/content/help/miners/mordor/gminer.md
  24. 25
      new-web/content/help/miners/mordor/lolminer.md
  25. 96
      new-web/i18n/en.json
  26. 96
      new-web/i18n/es.json
  27. 96
      new-web/i18n/ru.json
  28. 96
      new-web/i18n/zh.json
  29. 17
      new-web/jest.config.js
  30. 12
      new-web/jsconfig.json
  31. 409
      new-web/layouts/default.vue
  32. 42
      new-web/layouts/error.vue
  33. 114
      new-web/nuxt.config.js
  34. 42
      new-web/package.json
  35. 312
      new-web/pages/account/_id.vue
  36. 117
      new-web/pages/blocks.vue
  37. 109
      new-web/pages/help.vue
  38. 200
      new-web/pages/index.vue
  39. 47
      new-web/pages/payments.vue
  40. 93
      new-web/params/README.md
  41. 51
      new-web/params/example.config.json
  42. 35
      new-web/params/networks.json
  43. 7
      new-web/plugins/README.md
  44. BIN
      new-web/screenshots/01.png
  45. BIN
      new-web/screenshots/02.png
  46. 62
      new-web/scss/_custom.scss
  47. 11
      new-web/scss/main.scss
  48. BIN
      new-web/static/banner.jpg
  49. 1
      new-web/static/etc.svg
  50. BIN
      new-web/static/eth.png
  51. BIN
      new-web/static/favicon.png
  52. 1
      new-web/static/ubq.svg
  53. 122
      new-web/store/index.js
  54. 39
      new-web/test/ExplorerLink.spec.js
  55. 12438
      new-web/yarn.lock

16
new-web/.babelrc

@ -0,0 +1,16 @@
{
"env": {
"test": {
"presets": [
[
"@babel/preset-env",
{
"targets": {
"node": "current"
}
}
]
]
}
}
}

13
new-web/.editorconfig

@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

19
new-web/.eslintrc.js

@ -0,0 +1,19 @@
module.exports = {
root: true,
env: {
browser: true,
node: true,
},
parserOptions: {
parser: 'babel-eslint',
},
extends: [
'@nuxtjs',
'prettier',
'plugin:prettier/recommended',
'plugin:nuxt/recommended',
],
plugins: ['prettier'],
// add your custom rules here
rules: {},
}

17
new-web/.github/dependabot.yml

@ -0,0 +1,17 @@
version: 2
updates:
# Fetch and update latest `npm` packages
- package-ecosystem: npm
directory: '/'
schedule:
interval: daily
time: '00:00'
open-pull-requests-limit: 10
reviewers:
- iquidus
assignees:
- iquidus
commit-message:
prefix: fix
prefix-development: chore
include: scope

5
new-web/.github/semantic.yml

@ -0,0 +1,5 @@
# Always validate the PR title AND all the commits
titleAndCommits: true
# Allows use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
allowMergeCommits: true

50
new-web/.github/workflows/ci.yml

@ -0,0 +1,50 @@
name: ci
on:
push:
branches:
- master
pull_request:
branches:
- master
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest]
node: [14]
steps:
- name: Checkout 🛎
uses: actions/checkout@master
- name: Setup node env 🏗
uses: actions/setup-node@v2.1.5
with:
node-version: ${{ matrix.node }}
check-latest: true
- name: Get yarn cache directory path 🛠
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Cache node_modules 📦
uses: actions/cache@v2
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- name: Install dependencies 👨🏻💻
run: yarn
- name: Run linter 👀
run: yarn lint
- name: Run tests 🧪
run: yarn test

93
new-web/.gitignore vendored

@ -0,0 +1,93 @@
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
/logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# Nuxt generate
dist
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless
# IDE / Editor
.idea
# Service worker
sw.*
# macOS
.DS_Store
# Vim swap files
*.swp
# params/config.json
params/config.json

1
new-web/.husky/.gitignore vendored

@ -0,0 +1 @@
_

4
new-web/.husky/pre-commit

@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
yarn lint

4
new-web/.prettierrc

@ -0,0 +1,4 @@
{
"semi": false,
"singleQuote": true
}

61
new-web/README.md

@ -0,0 +1,61 @@
# vue-core-pool
vue based frontend for core-pool
## Install
```bash
# clone the repo
git clone https://github.com/etclabscore/vue-core-pool.git
cd vue-core-pool
# configure
cp params/example.config.json params/config.json
nano params/config.json
```
See: [params/README.md](https://github.com/etclabscore/vue-core-pool/blob/master/params/README.md) for more details.
## Build Setup
```bash
# install dependencies
$ yarn
# serve with hot reload at localhost:3000
$ yarn dev
# generate static project
$ yarn generate
```
For detailed explanation on how things work, check out [Nuxt.js docs](https://nuxtjs.org).
## Example caddy v2 config
```
{
email your@email.com
}
your.pool.domain.com {
file_server
root * /home/etclabscore/vue-core-pool/dist
try_files {path} /index.html
encode gzip
}
```
## Development
vue-core-pool is built using [Vue.js](https://vuejs.org/), [NuxtJS](https://nuxtjs.org/), and [Vuetify](https://vuetifyjs.com/). If modifying/contributing a basic understanding of these frameworks is recommended.
## screenshots
### index page
![index/miners page](/screenshots/01.png?raw=true "index/miners page")
### pool blocks page
![pool blocks page](/screenshots/02.png?raw=true "pool blocks page")

4
new-web/assets/variables.scss

@ -0,0 +1,4 @@
// Ref: https://github.com/nuxt-community/vuetify-module#customvariables
//
// The variables you want to modify
// $font-size-root: 20px;

122
new-web/components/ExplorerLink.vue

@ -0,0 +1,122 @@
<template>
<a :href="formatUrl()" target="_blank">{{ formatHash(hash, clip) }}</a>
</template>
<script>
export default {
props: {
hash: {
// the tx or block hash/number
type: String,
default() {
return '0x0'
},
},
linkType: {
// link type (tx or block)
type: String,
default() {
return 'tx'
},
},
clip: {
// shorten inner url to (0x[clip][separator][clip]) characters. 0 = dont clip.
type: Number,
default() {
return 0
},
},
separator: {
type: String,
default() {
return '...'
},
},
config: {
// config
type: Object,
default() {
return {}
},
},
},
methods: {
formatUrl() {
// see: https://github.com/ethereum/EIPs/pull/3091
// explorer.type
// expedition, blockscout, etherscan, etherchain, spectrum
// note, most of these are fairly similar however keeping all as options incase of future deviations.
const url = this.config.explorer.url
const type = this.config.explorer.type
let network = this.config.network
const symbol = this.config.symbol.toLowerCase()
let append = '/'
// handle link type deviations
switch (this.linkType) {
case 'block':
if (type === 'blockscout') {
append = append + 'blocks/'
} else {
// etherscan, etherchain or expedition, spectrum
append = append + 'block/'
}
break
case 'account':
if (type === 'etherchain') {
append = append + 'account/'
} else {
// etherscan, blockscout, expedition, spectrum
append = append + 'address/'
}
break
case 'token':
if (type === 'blockscout') {
append = append + 'tokens/'
} else {
// etherscan, etherchain, expedition, spectrum
append = append + 'token/'
}
break
case 'tx':
append = append + 'tx/' // yayyy conformity
break
default:
// o.O something very odd has occured O.o
// check your link-type argument, it should be:
// block, account, token, or tx
}
// handle network deviations
switch (type) {
case 'expedition':
if (network === 'classic') {
network = 'mainnet'
}
append = append + this.hash + '?network=' + network
break
case 'blockscout':
if (network === 'classic') {
network = 'mainnet'
}
append = '/' + symbol + '/' + network + append + this.hash
break
default:
append = append + this.hash
}
return url + append
},
formatHash(hash, len) {
if (hash === '0x0' || !hash) {
return 'N/A'
}
if (len > 0) {
const start = hash.substring(0, len + 2)
const end = hash.substring(hash.length - len)
return start + this.separator + end
}
return hash
},
},
}
</script>

134
new-web/components/tables/Blocks.vue

@ -0,0 +1,134 @@
<template>
<v-card flat tile>
<v-card-title>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
:label="$t('pages.blocks.search')"
single-line
outlined
hide-details
></v-text-field>
</v-card-title>
<v-data-table
dense
:headers="headers"
:items="blocks"
:footer-props="{
itemsPerPageText: $t('pages.blocks.blocksPerPage'),
itemsPerPageOptions: [25, 50, 100],
}"
:items-per-page="25"
:search="search"
:no-data-text="noDataText"
>
<template #[`item.height`]="{ item }">
{{ nf.format(item.height) }}
</template>
<template #[`item.shares`]="{ item }">
{{ nf.format(((item.shares / item.difficulty) * 100).toFixed(0)) }}%
</template>
<template #[`item.uncle`]="{ item }">
<v-chip label small :color="formatBlockType(item).color">{{
formatBlockType(item).text
}}</v-chip>
</template>
<template #[`item.timestamp`]="{ item }">
{{ dtf.format(item.timestamp * 1000) }}
</template>
<template #[`item.hash`]="{ item }">
<explorer-link
:hash="item.hash"
link-type="block"
:clip="8"
:config="config"
/>
</template>
<template #[`item.reward`]="{ item }">
{{ formatReward(item.reward).toFixed(6) }}
</template>
</v-data-table>
</v-card>
</template>
<script>
import ExplorerLink from '~/components/ExplorerLink'
export default {
components: {
ExplorerLink,
},
props: {
blocks: {
type: Array,
default() {
return []
},
},
config: {
type: Object,
default() {
return {}
},
},
noDataText: {
type: String,
default() {
return 'No blocks'
},
},
},
data() {
return {
search: null,
dtf: new Intl.DateTimeFormat('en', {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
}),
nf: new Intl.NumberFormat(this.locale, {}),
}
},
computed: {
headers() {
return [
{
text: this.$t('pages.blocks.blockNumber'),
align: 'start',
value: 'height',
},
{ text: this.$t('pages.blocks.blockHash'), value: 'hash' },
{ text: this.$t('pages.blocks.timeFound'), value: 'timestamp' },
{ text: this.$t('pages.blocks.variance'), value: 'shares' },
{
text:
this.$t('pages.blocks.reward') + ' (' + this.config.symbol + ')',
align: 'right',
value: 'reward',
},
{ text: this.$t('pages.blocks.type'), value: 'uncle', align: 'right' },
]
},
locale() {
return this.$i18n.locale
},
},
methods: {
formatBlockType(block) {
if (!block.uncle && !block.orphan) {
return { color: 'success', text: this.$t('pages.blocks.block') }
} else if (block.uncle) {
return { color: 'warning', text: this.$t('pages.blocks.uncle') }
} else {
return { color: 'error', text: this.$t('pages.blocks.orphan') }
}
},
formatReward(wei) {
return wei / 1000000000000000000
},
},
}
</script>

126
new-web/components/tables/Payments.vue

@ -0,0 +1,126 @@
<template>
<v-card flat tile>
<v-card-title>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
:label="$t('pages.payments.search')"
single-line
outlined
hide-details
></v-text-field>
</v-card-title>
<v-data-table
dense
:headers="headers"
:items="payments"
:footer-props="{
itemsPerPageText: $t('pages.payments.paymentsPerPage'),
itemsPerPageOptions: [25, 50, 100],
}"
:items-per-page="25"
:search="search"
:no-data-text="$t('pages.payments.noPayments')"
>
<template #[`item.timestamp`]="{ item }">
{{ dtf.format(item.timestamp * 1000) }}
</template>
<template #[`item.address`]="{ item }">
<nuxt-link :to="'/account/' + item.address">{{
formatAccountHash(item.address)
}}</nuxt-link>
</template>
<template #[`item.tx`]="{ item }">
<explorer-link :hash="item.tx" :config="config" :clip="12" />
</template>
<template #[`item.amount`]="{ item }">
{{ formatReward(item.amount) }} {{ symbol }}
</template>
</v-data-table>
</v-card>
</template>
<script>
import ExplorerLink from '~/components/ExplorerLink'
export default {
components: {
ExplorerLink,
},
props: {
payments: {
type: Array,
default() {
return []
},
},
headers: {
type: Array,
default() {
return [
{
text: this.$t('pages.payments.time'),
align: 'start',
value: 'timestamp',
},
{ text: this.$t('pages.payments.address'), value: 'address' },
{ text: this.$t('pages.payments.txid'), value: 'tx' },
{
text: this.$t('pages.payments.amount'),
value: 'amount',
align: 'right',
},
]
},
},
config: {
type: Object,
default() {
return {}
},
},
noDataText: {
type: String,
default() {
return this.$t('pages.payments.noPayments')
},
},
},
data() {
return {
search: null,
symbol: this.config.symbol,
nf: new Intl.NumberFormat(this.locale, {}),
dtf: new Intl.DateTimeFormat(this.locale, {
year: 'numeric',
month: 'numeric',
day: 'numeric',
hour: 'numeric',
minute: 'numeric',
second: 'numeric',
}),
}
},
computed: {
now() {
return this.$store.state.now
},
locale() {
return this.$i18n.locale
},
},
methods: {
formatAccountHash(account) {
if (account === '0x0' || !account) {
return 'N/A'
}
const start = account.substring(0, 10)
const end = account.substring(account.length - 10)
return start + '...' + end
},
formatReward(shannon) {
return shannon / 1000000000
},
},
}
</script>

9
new-web/content/help/miners/classic/gminer.md

@ -0,0 +1,9 @@
---
title: GMiner
minVer: 2.30
releases: https://github.com/develsoftware/GMinerRelease/releases
---
```
gminer --algo etchash --server STRATUM_HOST --user 0xda904bc07fd95e39661941b3f6daded1b8a38c71
```

25
new-web/content/help/miners/classic/lolminer.md

@ -0,0 +1,25 @@
---
title: lolMiner
minVer: 1.12
releases: https://github.com/Lolliedieb/lolMiner-releases/releases
---
```
#!/bin/bash
#################################
## Begin of user-editable part ##
#################################
POOL=STRATUM_HOST
# Address to send funds to. Change this address to yours!
WALLET=0xda904bc07fd95e39661941b3f6daded1b8a38c71
#################################
## End of user-editable part ##
#################################
cd "$(dirname "$0")"
./lolMiner -c ETC --pool $POOL --user $WALLET --ethstratum=ETHPROXY $@
```

15
new-web/content/help/miners/classic/nanominer.md

@ -0,0 +1,15 @@
---
title: nanominer
minVer: 1.12.0
releases: https://github.com/nanopool/nanominer/releases
---
```
; Address to send funds to. Change this address to yours!
wallet = 0xda904bc07fd95e39661941b3f6daded1b8a38c71
; Coin to mine.
coin = ETC
pool1=STRATUM_HOST
```

9
new-web/content/help/miners/classic/nbminer.md

@ -0,0 +1,9 @@
---
title: NBMiner
minVer: 33.4
releases: https://github.com/NebuTech/NBMiner/releases
---
```
nbminer -a etchash -o ethproxy+tcp://STRATUM_HOST -u 0xda904bc07fd95e39661941b3f6daded1b8a38c71
```

9
new-web/content/help/miners/classic/srbminer.md

@ -0,0 +1,9 @@
---
title: SRBMiner
minVer: 0.5.6
releases: https://github.com/doktor83/SRBMiner-Multi/releases
---
```
SRBMiner-MULTI --disable-cpu --algorithm etchash --pool STRATUM_HOST --wallet 0xda904bc07fd95e39661941b3f6daded1b8a38c71 --gpu-boost 5
```

9
new-web/content/help/miners/classic/teamred.md

@ -0,0 +1,9 @@
---
title: Team Red Miner
minVer: 0.7.18
releases: https://github.com/todxx/teamredminer/releases
---
```
teamredminer -a etchash -o stratum+tcp://STRATUM_HOST -u 0xda904bc07fd95e39661941b3f6daded1b8a38c71 -p x
```

9
new-web/content/help/miners/classic/trex.md

@ -0,0 +1,9 @@
---
title: T-Rex
minVer: 0.18.8
releases: https://github.com/trexminer/T-Rex/releases
---
```
t-rex -a etchash -o stratum+tcp://STRATUM_HOST -u 0xda904bc07fd95e39661941b3f6daded1b8a38c71 -p x -w rig0
```

9
new-web/content/help/miners/mordor/gminer.md

@ -0,0 +1,9 @@
---
title: GMiner
minVer: 2.30
releases: https://github.com/develsoftware/GMinerRelease/releases
---
```
gminer --algo etchash_test --server STRATUM_HOST --user 0xda904bc07fd95e39661941b3f6daded1b8a38c71
```

25
new-web/content/help/miners/mordor/lolminer.md

@ -0,0 +1,25 @@
---
title: lolMiner
minVer: 1.12
releases: https://github.com/Lolliedieb/lolMiner-releases/releases
---
```
#!/bin/bash
#################################
## Begin of user-editable part ##
#################################
POOL=STRATUM_HOST
# Address to send funds to. Change this address to yours!
WALLET=0xda904bc07fd95e39661941b3f6daded1b8a38c71
#################################
## End of user-editable part ##
#################################
cd "$(dirname "$0")"
./lolMiner -c ETC --pool $POOL --user $WALLET --ecip1099-activation 84 --ethstratum=ETHPROXY $@
```

96
new-web/i18n/en.json

@ -0,0 +1,96 @@
{
"menu": {
"home": "Home",
"blocks": "Pool Blocks",
"payments": "Payments",
"help": "Help",
"miningPool": "mining pool",
"minimize": "Minimize"
},
"info": {
"pool": {
"title": "POOL",
"hashrate": "Hashrate",
"lastBlock": "Last Block",
"miners": "Online",
"fee": "Fee"
},
"network": {
"title": "NETWORK",
"height": "Block Height",
"difficulty": "Difficulty",
"hashrate": "Hashrate",
"epoch": "Epoch",
"dag": "DAG"
},
"hide": "Hide"
},
"pages": {
"home": {
"testnetAlert": "This pool is configured for the {title}. The {symbol} rewarded is testnet {symbol}.",
"minimumPayout": "Min. payout threshold: {threshold} {symbol}.",
"mode": "{mode} stable and profitable pool with regular payouts.",
"poweredBy": "Powered by",
"protocols": "Getwork & Stratum supported.",
"account": "Account",
"hashrate": "Hashrate",
"lastBeat": "Last Beat",
"noMiners": "No miners connected",
"minersPerPage": "Miners per page",
"search": "Search by account"
},
"blocks": {
"blocks": "Blocks",
"shares": "Shares/Diff",
"uncleRate": "Uncle Rate",
"orphanRate": "Orphan Rate",
"immature": "Immature",
"newBlocks": "New Blocks",
"noMatured": "No matured blocks",
"noImmature": "No immature blocks",
"noPending": "No pending blocks",
"blocksPerPage": "Blocks per page",
"search": "Search by number or hash",
"blockNumber": "Block Number",
"blockHash": "Block Hash",
"timeFound": "Time Found",
"variance": "Variance",
"reward": "Reward",
"type": "Type",
"block": "Block",
"uncle": "Uncle",
"orphan": "Orphan"
},
"payments": {
"latestPayments": "Latest Payments",
"noPayments": "No payments found",
"paymentsPerPage": "Payments per page",
"search": "Search by address or txn hash",
"time": "Time",
"address": "Address",
"txid": "Tx ID",
"amount": "Amount"
},
"account": {
"immatureBal": "IMMATURE BAL.",
"pendingBal": "PENDING BAL.",
"totalPaid": "TOTAL PAID",
"lastShare": "LAST SHARE",
"hashrate30min": "HASHRATE (30min)",
"hashrate3hour": "HASHRATE (3h)",
"blocksFound": "BLOCKS FOUND",
"workersOnline": "WORKERS ONLINE",
"roundShare": "YOUR ROUND SHARE",
"info": "Your average hashrate will be smoothly adjusted until you have shares to fullfill estimation window. There are two windows, long and short, first is equal to about 30 minutes and long window is usually equal to 3 hours. Dead (sick) workers will be highlighted in a table of workers if they didn't submit a share for 1/2 of short window, so you can perform maintenance of your rigs.",
"jsonApi": "Your bulk stats JSON API URL:",
"workers": "Workers",
"payments": "Payments",
"worker": {
"id": "ID",
"hashrateShort": "Hashrate (rough, short average)",
"hashrateLong": "Hashrate (accurate, long average)",
"lastShare": "Last Share"
}
}
}
}

96
new-web/i18n/es.json

@ -0,0 +1,96 @@
{
"menu": {
"home": "Casa",
"blocks": "Bloques de grupo",
"payments": "Pagos",
"help": "Ayuda",
"miningPool": "grupo de minería",
"minimize": "Minimizar"
},
"info": {
"pool": {
"title": "GRUPO",
"hashrate": "Velocidad",
"lastBlock": "Último Bloque",
"miners": "En línea",
"fee": "Cuota"
},
"network": {
"title": "RED",
"height": "Altura del bloque",
"difficulty": "Dificultad",
"hashrate": "Velocidad",
"epoch": "Época",
"dag": "DAG"
},
"hide": "Esconder"
},
"pages": {
"home": {
"testnetAlert": "Este grupo está configurado para el {title}. El {symbol} premiado es testnet {symbol}.",
"minimumPayout": "Min. umbral de pago: {threshold} {symbol}.",
"mode": "{mode} Grupo estable y rentable con pagos regulares.",
"poweredBy": "Energizado por",
"protocols": "Compatible con Getwork y Stratum.",
"account": "Cuenta",
"hashrate": "Velocidad",
"lastBeat": "último latido",
"noMiners": "No hay mineros conectados",
"minersPerPage": "Mineros por página",
"search": "Buscar por cuenta"
},
"blocks": {
"blocks": "Bloques",
"shares": "Acciones/Dificultad",
"uncleRate": "Tío ocurrencia",
"orphanRate": "Ocurrencia huérfana",
"immature": "Inmaduro",
"newBlocks": "Nuevos bloques",
"noMatured": "Sin bloques maduros",
"noImmature": "Sin bloques inmaduros",
"noPending": "Sin bloques pendientes",
"blocksPerPage": "Bloques por página",
"search": "Buscar por número o hash",
"blockNumber": "Número de bloque",
"blockHash": "Bloquear huella digital",
"timeFound": "Tiempo encontrado",
"variance": "Diferencia",
"reward": "Recompensa",
"type": "Tipo",
"block": "Bloquear",
"uncle": "Tío",
"orphan": "Huérfano"
},
"payments": {
"latestPayments": "Últimos pagos",
"noPayments": "No se encontraron pagos",
"paymentsPerPage": "Pagos por página",
"search": "Buscar por dirección o hash txn",
"time": "Hora",
"address": "la dirección",
"txid": "ID de transacción",
"amount": "Cantidad"
},
"account": {
"immatureBal": "EQUILIBRIO INMATURO",
"pendingBal": "EQUILIBRIO PENDIENTE",
"totalPaid": "TOTAL PAGADO",
"lastShare": "ÚLTIMA COMPARTIR",
"hashrate30min": "VELOCIDAD (30min)",
"hashrate3hour": "VELOCIDAD (3h)",
"blocksFound": "BLOQUES ENCONTRADOS",
"workersOnline": "TRABAJADORES EN LINEA",
"roundShare": "SU PARTICIPACIÓN RONDA",
"info": "Su tasa de hash promedio se ajustará sin problemas hasta que tenga acciones para completar la ventana de estimación. Hay dos ventanas, larga y corta, la primera es igual a unos 30 minutos y la ventana larga suele ser igual a 3 horas. Los trabajadores muertos (enfermos) se destacarán en una tabla de trabajadores si no enviaron una participación durante la mitad del período corto, para que pueda realizar el mantenimiento de sus equipos.",
"jsonApi": "Su URL de API JSON de estadísticas masivas:",
"workers": "Trabajadores",
"payments": "Pagos",
"worker": {
"id": "ID",
"hashrateShort": "Velocidad (aproximado, promedio corto)",
"hashrateLong": "Velocidad (preciso, promedio largo)",
"lastShare": "Último compartir"
}
}
}
}

96
new-web/i18n/ru.json

@ -0,0 +1,96 @@
{
"menu": {
"home": "Главная",
"blocks": "Блоки бассейна",
"payments": "Платежи",
"help": "Помогите",
"miningPool": "майнинг пул",
"minimize": "Минимизировать"
},
"info": {
"pool": {
"title": "БАССЕЙН",
"hashrate": "СКОРОСТЬ",
"lastBlock": "Последний блок",
"miners": "В сети",
"fee": "Гонорар"
},
"network": {
"title": "СЕТЬ",
"height": "Высота блока",
"difficulty": "Сложность",
"hashrate": "СКОРОСТЬ",
"epoch": "Эпоха",
"dag": "DAG"
},
"hide": "Спрятать"
},
"pages": {
"home": {
"testnetAlert": "Этот пул настроен для {title}. Вознаграждение {symbol} - это тестовая сеть {symbol}.",
"minimumPayout": "Мин. порог выплаты: {threshold} {symbol}.",
"mode": "{mode} стабильный и прибыльный пул с регулярными выплатами.",
"poweredBy": "Питаться от",
"protocols": "Getwork & Stratum поддерживается.",
"account": "Счет",
"hashrate": "СКОРОСТЬ",
"lastBeat": "Последний удар",
"noMiners": "Майнеры не подключены",
"minersPerPage": "Майнеров на страницу",
"search": "Поиск по аккаунту"
},
"blocks": {
"blocks": "Блоки",
"shares": "Доли / Сложность",
"uncleRate": "Дядя Ставка",
"orphanRate": "Уровень сирот",
"immature": "Незрелый",
"newBlocks": "Новые блоки",
"noMatured": "Нет зрелых блоков",
"noImmature": "Нет незрелых блоков",
"noPending": "Нет ожидающих блоков",
"blocksPerPage": "Блоки на страницу",
"search": "Поиск по номеру или хешу",
"blockNumber": "Номер блока",
"blockHash": "Блокировать хеш",
"timeFound": "Время найдено",
"variance": "Дисперсия",
"reward": "Награда",
"type": "Тип",
"block": "Блокировать",
"uncle": "Дядя",
"orphan": "Сирота"
},
"payments": {
"latestPayments": "Последние платежи",
"noPayments": "Платежей не найдено",
"paymentsPerPage": "Платежей за страницу",
"search": "Поиск по адресу или txn-хешу",
"time": "Время",
"address": "Адрес",
"txid": "номер транзакции",
"amount": "Количество"
},
"account": {
"immatureBal": "НЕПРЕРЫВНЫЙ БАЛАНС",
"pendingBal": "ОЖИДАНИЕ БАЛАНСА",
"totalPaid": "ИТОГО",
"lastShare": "ПОСЛЕДНЯЯ ПОДЕЛИТЬСЯ",
"hashrate30min": "СКОРОСТЬ (30 минут)",
"hashrate3hour": "СКОРОСТЬ (3 часа)",
"blocksFound": "БЛОКИ НАЙДЕНЫ",
"workersOnline": "РАБОТНИКИ ОНЛАЙН",
"roundShare": "ВАША КРУГЛАЯ АКЦИЯ",
"info": "Ваш средний хешрейт будет плавно регулироваться, пока у вас не будет долей для полного заполнения окна оценки. Есть два окна, длинное и короткое, первое составляет около 30 минут, а длинное окно обычно равно 3 часам. Мертвые (больные) рабочие будут выделены в таблице рабочих, если они не предоставили долю в течение 1/2 короткого периода, чтобы вы могли выполнять техническое обслуживание своих установок.",
"jsonApi": "URL вашей массовой статистики JSON API:",
"workers": "Рабочие",
"payments": "Платежи",
"worker": {
"id": "Я БЫ",
"hashrateShort": "Скорость (грубая, короткая средняя)",
"hashrateLong": "Скорость (точная, длинная средняя)",
"lastShare": "Последняя доля"
}
}
}
}

96
new-web/i18n/zh.json

@ -0,0 +1,96 @@
{
"menu": {
"home": "开始",
"blocks": "区块",
"payments": "付款方式",
"help": "帮助",
"miningPool": "矿池",
"minimize": "最小化"
},
"info": {
"pool": {
"title": "名称",
"hashrate": "哈希率",
"lastBlock": "最新区块",
"miners": "矿工",
"fee": "费用"
},
"network": {
"title": "名称",
"height": "块高",
"difficulty": "难度",
"hashrate": "哈希率",
"epoch": "Epoch",
"dag": "DAG"
},
"hide": "隐藏"
},
"pages": {
"home": {
"testnetAlert": "已为{title}配置了该池。 奖励的{symbol}是测试网{symbol}。",
"minimumPayout": "最低付款额度:{threshold} {symbol}。",
"mode": "{mode}稳定且可盈利的定期付款池。",
"poweredBy": "基于",
"protocols": "支持Getwork和Stratum。",
"account": "帐户",
"hashrate": "哈希率",
"lastBeat": "最近上线",
"noMiners": "无连接矿工",
"minersPerPage": "每页矿工数",
"search": "搜索"
},
"blocks": {
"blocks": "区块",
"shares": "份额",
"uncleRate": "叔块率",
"orphanRate": "孤块率",
"immature": "待确认",
"newBlocks": "新区块",
"noMatured": "无已确认的区块",
"noImmature": "无待确认的区块",
"noPending": "无待处理的区块",
"blocksPerPage": "每页块数",
"search": "搜索",
"blockNumber": "块号",
"blockHash": "区块哈希",
"timeFound": "发现时间",
"variance": "回报方差",
"reward": "奖励",
"type": "类型",
"block": "区块",
"uncle": "叔块",
"orphan": "孤块"
},
"payments": {
"latestPayments": "最新付款",
"noPayments": "无付款记录",
"paymentsPerPage": "每页付款数",
"search": "搜索",
"time": "时间",
"address": "地址",
"txid": "交易编号",
"amount": "数量"
},
"account": {
"immatureBal": "未确认的余额",
"pendingBal": "待确认余额",
"totalPaid": "总支付",
"lastShare": "最近份额",
"hashrate30min": "哈希率(30分钟)",
"hashrate3hour": "哈希率(3小时)",
"blocksFound": "发现的块",
"workersOnline": "在线矿工",
"roundShare": "您的份额",
"info": "您的平均哈希率将被平滑地调整,直到您有要完全满足估计值的份额为止。有长、短两个时间窗口,前者通常等于3小时,后者等于大约30分钟。列表中会显示未在短窗口的1/2时间内提交份额的矿工,以便您进行矿机维护。",
"jsonApi": "您的批量统计信息JSON API URL:",
"workers": "矿工",
"payments": "付款方式",
"worker": {
"id": "ID",
"hashrateShort": "哈希率(粗略,短期平均)",
"hashrateLong": "哈希率(准确,长期平均)",
"lastShare": "最近份额"
}
}
}
}

17
new-web/jest.config.js

@ -0,0 +1,17 @@
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1',
'^vue$': 'vue/dist/vue.common.js',
},
moduleFileExtensions: ['js', 'vue', 'json'],
transform: {
'^.+\\.js$': 'babel-jest',
'.*\\.(vue)$': 'vue-jest',
},
collectCoverage: true,
collectCoverageFrom: [
'<rootDir>/components/**/*.vue',
'<rootDir>/pages/**/*.vue',
],
}

12
new-web/jsconfig.json

@ -0,0 +1,12 @@
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": ["./*"],
"@/*": ["./*"],
"~~/*": ["./*"],
"@@/*": ["./*"]
}
},
"exclude": ["node_modules", ".nuxt", "dist"]
}

409
new-web/layouts/default.vue

@ -0,0 +1,409 @@
<template>
<v-app>
<v-navigation-drawer
v-model="drawer"
:mini-variant="miniVariant"
mobile-breakpoint="sm"
clipped
fixed
app
>
<v-list>
<v-list-item
v-for="(item, i) in items"
:key="i"
:to="item.to"
router
exact
>
<v-list-item-action>
<v-icon>{{ item.icon }}</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title v-text="item.title" />
</v-list-item-content>
</v-list-item>
</v-list>
<template #append>
<v-list>
<v-list-item
v-for="(item, index) in stats.env.extraPools"
:key="index"
:href="item.url"
target="_blank"
>
<v-list-item-action size="24">
<img
:src="require('~/static/' + stats.networks[item.network].icon)"
style="width: 24px; max-height: 24px"
/>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{
stats.networks[item.network].title
}}</v-list-item-title>
<v-list-item-subtitle
>{{ item.type }}
{{ $t('menu.miningPool') }}</v-list-item-subtitle
>
</v-list-item-content>
</v-list-item>
<v-list-item @click.stop="miniVariant = !miniVariant">
<v-list-item-action>
<v-icon
>mdi-{{ `chevron-${miniVariant ? 'right' : 'left'}` }}</v-icon
>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ $t('menu.minimize') }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</template>
</v-navigation-drawer>
<v-app-bar clipped-left clipped-right fixed app>
<v-app-bar-nav-icon
:class="{ 'd-xs-flex': true, 'd-md-none': drawer }"
@click.stop="drawer = !drawer"
/>
<v-spacer />
<v-toolbar-title>
<v-avatar size="32">
<img :src="require('@/static/' + logo)" />
</v-avatar>
{{ title }}
</v-toolbar-title>
<v-spacer />
<v-btn
icon
class="mr-1"
@click.stop="$vuetify.theme.dark = !$vuetify.theme.dark"
>
<v-icon>mdi-theme-light-dark</v-icon>
</v-btn>
<v-menu offset-y>
<template #activator="{ on, attrs }">
<v-btn icon v-bind="attrs" class="mr-1" v-on="on">
<v-icon>mdi-translate</v-icon>
</v-btn>
</template>
<v-list>
<v-list-item
v-for="(item, index) in locales"
:key="index"
:disabled="item.code === $i18n.locale"
@click="$i18n.setLocale(item.code)"
>
<v-list-item-title>{{ item.name }}</v-list-item-title>
</v-list-item>
</v-list>
</v-menu>
<v-app-bar-nav-icon @click.stop="drawerRight = !drawerRight" />
</v-app-bar>
<v-navigation-drawer
v-model="drawerRight"
mobile-breakpoint="sm"
clipped
fixed
right
app
>
<v-list dense class="ma-0 pa-0">
<v-subheader>{{ $t('info.pool.title') }}</v-subheader>
<v-list-item class="stats-item ma-1 darken2">
<v-list-item-avatar>
<v-icon>mdi-gauge</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('info.pool.hashrate')
}}</v-list-item-title>
<v-list-item-subtitle>{{
formatHashrate(stats.poolHashRate, true)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-clock-outline</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('info.pool.lastBlock')
}}</v-list-item-title>
<v-list-item-subtitle>{{
formatTimeSince(stats.lastBlockFound)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-pickaxe</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ $t('info.pool.miners') }}</v-list-item-title>
</v-list-item-content>
<v-list-item-action-text>{{
stats.minersOnline
}}</v-list-item-action-text>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-cash</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ $t('info.pool.fee') }}</v-list-item-title>
</v-list-item-content>
<v-list-item-action-text
>{{ stats.env.poolFee }}%</v-list-item-action-text
>
</v-list-item>
</v-list>
<v-list dense class="ma-0 pa-0">
<v-subheader text-right>{{ $t('info.network.title') }}</v-subheader>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<img :src="require('~/static/' + stats.env.network.icon)" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ stats.env.network.title }}</v-list-item-title>
<v-list-item-subtitle>{{
stats.env.network.algo
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-cube-scan</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('info.network.height')
}}</v-list-item-title>
<v-list-item-subtitle>{{
nf.format(stats.height)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-lock</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('info.network.difficulty')
}}</v-list-item-title>
<v-list-item-subtitle>{{
formatHashrate(stats.difficulty, false)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-gauge</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('info.network.hashrate')
}}</v-list-item-title>
<v-list-item-subtitle>{{
formatHashrate(stats.networkHashrate, true)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-timer-sand</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title
>{{ $t('info.network.epoch') }} ({{
stats.env.network.algo
}})</v-list-item-title
>
<v-list-item-subtitle>{{ stats.epoch }}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="stats-item ma-1">
<v-list-item-avatar>
<v-icon>mdi-chip</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{ $t('info.network.dag') }}</v-list-item-title>
<v-list-item-subtitle
>{{ stats.dagSize }} MByte</v-list-item-subtitle
>
</v-list-item-content>
</v-list-item>
</v-list>
<template #append>
<v-list>
<v-list-item @click.stop="drawerRight = !drawerRight">
<v-list-item-action>
<v-icon>mdi-chevron-right</v-icon>
</v-list-item-action>
<v-list-item-content>
<v-list-item-title>{{ $t('info.hide') }}</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
</template>
</v-navigation-drawer>
<v-main>
<v-container fluid class="pa-0">
<nuxt />
</v-container>
</v-main>
<v-footer :absolute="!fixed" app>
<span>&copy; {{ new Date().getFullYear() }}</span>
<v-spacer />
</v-footer>
</v-app>
</template>
<script>
import { formatDistance } from 'date-fns'
export default {
data() {
return {
drawer: true,
drawerRight: true,
fixed: true,
miniVariant: true,
title: this.$store.state.env.title,
logo: this.$store.state.env.logo,
nf: new Intl.NumberFormat(this.locale, {}),
timer: {
stats: null,
miners: null,
},
interval: {
stats: 2000,
miners: 10000,
blocks: 10000,
payments: 10000,
},
}
},
computed: {
stats() {
return this.$store.state
},
now() {
return this.$store.state.now
},
locales() {
return this.$i18n.locales
},
darkmode: {
get() {
return this.$vuetify.theme.dark
},
set() {
this.$vuetify.theme.dark = !this.$vuetify.theme.dark
},
},
items() {
return [
{
icon: 'mdi-home',
title: this.$t('menu.home'),
to: '/',
},
{
icon: 'mdi-cube-outline',
title: this.$t('menu.blocks'),
to: '/blocks',
},
{
icon: 'mdi-send',
title: this.$t('menu.payments'),
to: '/payments',
},
{
icon: 'mdi-help-circle-outline',
title: this.$t('menu.help'),
to: '/help',
},
]
},
locale() {
return this.$i18n.locale
},
},
created() {
this.startSync('stats')
this.startSync('miners')
this.startSync('blocks')
this.startSync('payments')
const t = this
setInterval(function () {
t.$store.dispatch('now')
}, 1000)
},
methods: {
formatHashrate(bytes, showHash) {
const sizes = ['', 'K', 'M', 'G', 'T']
if (bytes === 0) {
if (showHash) {
return '0 H'
}
return '0'
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)))
if (i === 0) {
return bytes + ' ' + sizes[i]
}
let unit = ' ' + sizes[i]
if (showHash) {
unit = ' ' + sizes[i] + 'H'
}
return (bytes / 1000 ** i).toFixed(3) + unit
},
startSync(store) {
const self = this
this.timer[store] = null
this.$store.dispatch(store)
this.timer[store] = setInterval(function () {
self.$store.dispatch(store)
}, this.interval[store])
},
stopSync(store) {
clearInterval(this.timer[store])
this.timer[store] = null
},
formatTimeSince(time) {
return formatDistance(new Date(time * 1000), this.now, {
addSuffix: true,
includeSeconds: true,
})
},
},
}
</script>
<style lang="scss" scoped>
.stats-item {
background-color: var(--v-secondary-base) !important;
border-bottom-left-radius: 32px !important;
border-top-left-radius: 32px !important;
}
</style>
<style lang="scss">
::-webkit-scrollbar {
width: 6px; /* for vertical scrollbars */
height: 6px; /* for horizontal scrollbars */
border-radius: 3px;
}
::-webkit-scrollbar-track {
background: var(--v-secondary-base) !important;
}
::-webkit-scrollbar-thumb {
background: var(--v-primary-base) !important;
border-radius: 3px;
}
</style>

42
new-web/layouts/error.vue

@ -0,0 +1,42 @@
<template>
<v-app dark>
<h1 v-if="error.statusCode === 404">
{{ pageNotFound }}
</h1>
<h1 v-else>
{{ otherError }}
</h1>
<NuxtLink to="/"> Home page </NuxtLink>
</v-app>
</template>
<script>
export default {
layout: 'empty',
props: {
error: {
type: Object,
default: null,
},
},
data() {
return {
pageNotFound: '404 Not Found',
otherError: 'An error occurred',
}
},
head() {
const title =
this.error.statusCode === 404 ? this.pageNotFound : this.otherError
return {
title,
}
},
}
</script>
<style scoped>
h1 {
font-size: 20px;
}
</style>

114
new-web/nuxt.config.js

@ -0,0 +1,114 @@
// import colors from 'vuetify/es5/util/colors'
import config from './params/config.json'
export default {
// Disable server-side rendering (https://go.nuxtjs.dev/ssr-mode)
ssr: false,
// Target (https://go.nuxtjs.dev/config-target)
target: 'static',
// Global page headers (https://go.nuxtjs.dev/config-head)
head: {
titleTemplate: '%s',
title: config.title,
meta: [
{ charset: 'utf-8' },
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
{ hid: 'description', name: 'description', content: config.description },
],
link: [{ rel: 'icon', type: 'image/x-icon', href: '/' + config.favicon }],
},
// Global CSS (https://go.nuxtjs.dev/config-css)
css: ['~/scss/main.scss'],
// Plugins to run before rendering page (https://go.nuxtjs.dev/config-plugins)
plugins: [],
// Auto import components (https://go.nuxtjs.dev/config-components)
components: true,
// Modules for dev and build (recommended) (https://go.nuxtjs.dev/config-modules)
buildModules: [
// https://github.com/nuxt-community/eslint-module
'@nuxtjs/eslint-module',
// https://go.nuxtjs.dev/vuetify
'@nuxtjs/vuetify',
],
// Modules (https://go.nuxtjs.dev/config-modules)
modules: [
// https://go.nuxtjs.dev/axios
'@nuxtjs/axios',
// https://go.nuxtjs.dev/pwa
'@nuxtjs/pwa',
// https://go.nuxtjs.dev/content
'@nuxt/content',
// https://i18n.nuxtjs.org/
'nuxt-i18n',
],
// Axios module configuration (https://go.nuxtjs.dev/config-axios)
axios: {},
// Content module configuration (https://go.nuxtjs.dev/config-content)
content: {},
// Vuetify module configuration (https://go.nuxtjs.dev/config-vuetify)
vuetify: {
customVariables: ['~/assets/variables.scss'],
theme: config.theme,
},
// i18n module configuration (https://i18n.nuxtjs.org/basic-usage)
i18n: {
locales: [
{
code: 'en',
name: 'English',
},
{
code: 'es',
name: 'Español',
},
{
code: 'ru',
name: 'Pусский',
},
{
code: 'zh',
name: '中文',
},
],
strategy: 'no_prefix',
detectBrowserLanguage: {
useCookie: true,
cookieKey: 'core_pool_i18n_redirected',
fallbackLocale: config.i18n.fallback || 'en',
alwaysRedirect: true,
onlyOnRoot: true,
},
defaultLocale: config.i18n.default || 'en',
vueI18n: {
fallbackLocale: config.i18n.fallback || 'en',
messages: {
en: require('./i18n/en.json'),
es: require('./i18n/es.json'),
ru: require('./i18n/ru.json'),
zh: require('./i18n/zh.json'),
},
},
},
// Build Configuration (https://go.nuxtjs.dev/config-build)
build: {},
// hooks
hooks: {
'content:file:beforeParse': (file) => {
if (file.extension !== '.md') return
file.data = file.data.replace(/STRATUM_HOST/g, config.stratum)
},
},
}

42
new-web/package.json

@ -0,0 +1,42 @@
{
"name": "vue-core-pool",
"version": "1.0.0",
"private": true,
"scripts": {
"dev": "nuxt",
"build": "nuxt build",
"lint": "eslint --ext .js,.vue --ignore-path .gitignore .",
"lint-fix": "eslint --ext .js,.vue --ignore-path .gitignore --fix .",
"start": "nuxt start",
"generate": "nuxt generate",
"test": "jest",
"prepare": "husky install"
},
"dependencies": {
"@nuxt/content": "^1.14.0",
"@nuxtjs/axios": "^5.13.6",
"@nuxtjs/pwa": "^3.3.5",
"consola": "^2.15.3",
"core-js": "^3.12.1",
"date-fns": "^2.23.0",
"nuxt": "^2.15.6",
"nuxt-i18n": "^6.27.0"
},
"devDependencies": {
"@nuxtjs/eslint-config": "^6.0.0",
"@nuxtjs/eslint-module": "^3.0.2",
"@nuxtjs/vuetify": "^1.12.1",
"@vue/test-utils": "^1.2.0",
"babel-core": "7.0.0-bridge.0",
"babel-eslint": "^10.1.0",
"babel-jest": "^26.5.0",
"eslint": "^7.27.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-nuxt": "^2.0.0",
"eslint-plugin-prettier": "^3.4.0",
"husky": "^6.0.0",
"jest": "^26.5.0",
"prettier": "^2.4.1",
"vue-jest": "^3.0.4"
}
}

312
new-web/pages/account/_id.vue

@ -0,0 +1,312 @@
<template>
<v-col cols="12" class="pa-0">
<v-row no-gutters class="border-bottom">
<v-col cols="12" md="4" sm="4" xs="12">
<v-list dense>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-cloud-outline</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.immatureBal')
}}</v-list-item-title>
<v-list-item-subtitle>
{{ formatEther(data.stats.immature) }} {{ config.symbol }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-bank</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.pendingBal')
}}</v-list-item-title>
<v-list-item-subtitle>
{{ formatEther(data.stats.balance) }} {{ config.symbol }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-cash</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.totalPaid')
}}</v-list-item-title>
<v-list-item-subtitle
>{{ formatEther(data.stats.paid) }}
{{ config.symbol }}</v-list-item-subtitle
>
</v-list-item-content>
</v-list-item>
</v-list>
</v-col>
<v-col cols="12" md="4" sm="4" xs="12">
<v-list dense>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-cube-send</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.lastShare')
}}</v-list-item-title>
<v-list-item-subtitle>{{
formatTimeSince(data.stats.lastShare)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-gauge-full</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.hashrate30min')
}}</v-list-item-title>
<v-list-item-subtitle
>{{ formatHashrate(data.currentHashrate, true) }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-gauge-full</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.hashrate3hour')
}}</v-list-item-title>
<v-list-item-subtitle
>{{ formatHashrate(data.hashrate, true) }}
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-col>
<v-col cols="12" md="4" sm="4" xs="12">
<v-list dense>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-cube-scan</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.blocksFound')
}}</v-list-item-title>
<v-list-item-subtitle>{{
nf.format(data.stats.blocksFound)
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-pickaxe</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.workersOnline')
}}</v-list-item-title>
<v-list-item-subtitle>{{
data.workersOnline
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
<v-list-item class="border-right">
<v-list-item-avatar>
<v-icon>mdi-clock-outline</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
$t('pages.account.roundShare')
}}</v-list-item-title>
<v-list-item-subtitle
>{{ data.roundShares }}%
</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-col>
</v-row>
<v-alert tile dismissible type="info">
{{ $t('pages.account.info') }}
</v-alert>
<v-alert tile dismissible type="info">
{{ $t('pages.account.jsonApi') }}
<a
:href="
config.api + '/accounts/0xda904bc07fd95e39661941b3f6daded1b8a38c71'
"
target="_blank"
style="color: #fff"
>
{{
config.api + '/accounts/0xda904bc07fd95e39661941b3f6daded1b8a38c71'
}}
</a>
</v-alert>
<v-tabs v-model="tab" grow>
<v-tab>
{{ $t('pages.account.workers')
}}<v-chip label x-small class="ml-2">{{ data.workersTotal }}</v-chip>
</v-tab>
<v-tab>
{{ $t('pages.account.payments')
}}<v-chip label x-small class="ml-2">{{ data.paymentsTotal }}</v-chip>
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item>
<v-simple-table fixed-header>
<template #default>
<thead>
<tr>
<th class="text-left">{{ $t('pages.account.worker.id') }}</th>
<th class="text-left">
{{ $t('pages.account.worker.hashrateShort') }}
</th>
<th class="text-left">
{{ $t('pages.account.worker.hashrateLong') }}
</th>
<th class="text-left">
{{ $t('pages.account.worker.lastShare') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, index) in data.workers" :key="index">
<td>{{ index }}</td>
<td>{{ formatHashrate(item.hr, true) }}</td>
<td>{{ formatHashrate(item.hr2, true) }}</td>
<td>{{ formatTimeSince(item.lastBeat) }}</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-tab-item>
<v-tab-item>
<payments-table
:payments="data.payments"
:headers="payoutHeaders"
:config="config"
:no-data-text="$t('pages.payments.noPayments')"
/>
</v-tab-item>
</v-tabs-items>
</v-col>
</template>
<script>
import axios from 'axios'
import { formatDistance } from 'date-fns'
import PaymentsTable from '~/components/tables/Payments'
export default {
components: {
PaymentsTable,
},
data() {
return {
id: this.$route.params.id,
errors: [],
tab: null,
data: {
workers: {},
workersOffline: 0,
workersOnline: 0,
workersTotal: 0,
roundShares: 0,
paymentsTotal: 0,
payments: null,
hashrate: 0,
currentHashrate: 0,
stats: {
balance: 0,
blocksFound: 0,
immature: 0,
lastShare: 0,
paid: 0,
pending: 0,
},
},
payoutHeaders: [
{
text: this.$t('pages.payments.time'),
align: 'start',
value: 'timestamp',
},
{ text: this.$t('pages.payments.txid'), value: 'tx' },
{
text: this.$t('pages.payments.amount'),
value: 'amount',
align: 'right',
},
],
nf: new Intl.NumberFormat(this.locale, {}),
}
},
computed: {
now() {
return this.$store.state.now
},
config() {
return this.$store.state.env
},
locale() {
return this.$i18n.locale
},
},
created() {
this.fetchData(this.id)
},
methods: {
async fetchData(address) {
try {
const { data } = await axios.get(
this.config.api + '/accounts/' + address
)
if (data) {
this.data = data
}
} catch (error) {
this.errors.push(error)
}
},
formatTimeSince(time) {
return formatDistance(new Date(time * 1000), this.now, {
addSuffix: true,
includeSeconds: true,
})
},
formatHashrate(bytes, showHash) {
const sizes = ['', 'K', 'M', 'G', 'T']
if (bytes === 0) {
return 'n/a'
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)))
if (i === 0) {
return bytes + ' ' + sizes[i]
}
let unit = ' ' + sizes[i]
if (showHash) {
unit = ' ' + sizes[i] + 'H'
}
return (bytes / 1000 ** i).toFixed(3) + unit
},
formatEther(shannon) {
const ether = shannon / 1000000000
// format nicely without losing precision
const split = ether.toString().split('.')
if (split.length > 1) {
return this.nf.format(split[0]) + '.' + split[1]
} else {
return this.nf.format(ether)
}
},
},
}
</script>

117
new-web/pages/blocks.vue

@ -0,0 +1,117 @@
<template>
<v-row justify="center" align="center" no-gutters>
<v-col cols="12" class="pa-0">
<v-card tile flat style="margin-bottom: 1px solid #2e2e2e">
<v-simple-table>
<template #default>
<thead>
<tr>
<th class="text-left">
{{ $t('pages.blocks.blocks') }}
</th>
<th class="text-left">
{{ $t('pages.blocks.shares') }}
</th>
<th class="text-left">
{{ $t('pages.blocks.uncleRate') }}
</th>
<th class="text-left">
{{ $t('pages.blocks.orphanRate') }}
</th>
</tr>
</thead>
<tbody>
<tr v-for="(item, key) in blocks.luck" :key="key">
<td>{{ key }}</td>
<td>{{ nf.format(item.luck.toFixed(2)) }}%</td>
<td>{{ nf.format((item.uncleRate * 100).toFixed(2)) }}%</td>
<td>{{ nf.format((item.orphanRate * 100).toFixed(2)) }}%</td>
</tr>
</tbody>
</template>
</v-simple-table>
</v-card>
<v-card tile flat>
<v-tabs v-model="tab" background-color="transparent" grow>
<v-tab
>{{ $t('pages.blocks.blocks')
}}<v-chip label small color="success" class="ml-2">{{
blocks.maturedTotal
}}</v-chip></v-tab
>
<v-tab
>{{ $t('pages.blocks.immature')
}}<v-chip label small color="warning" class="ml-2">{{
blocks.immatureTotal
}}</v-chip></v-tab
>
<v-tab
>{{ $t('pages.blocks.newBlocks')
}}<v-chip label small color="info" class="ml-2">{{
blocks.candidatesTotal
}}</v-chip></v-tab
>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item>
<blocks-table
:blocks="matured"
:config="config"
:no-data-text="$t('pages.blocks.noMatured')"
/>
</v-tab-item>
<v-tab-item>
<blocks-table
:blocks="immature"
:config="config"
:no-data-text="$t('pages.blocks.noImmature')"
/>
</v-tab-item>
<v-tab-item>
<blocks-table
:blocks="candidates"
:config="config"
:no-data-text="$t('pages.blocks.noPending')"
/>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-col>
</v-row>
</template>
<script>
import BlocksTable from '~/components/tables/Blocks'
export default {
components: {
BlocksTable,
},
data() {
return {
tab: null,
nf: new Intl.NumberFormat(this.locale, {}),
}
},
computed: {
blocks() {
return this.$store.state.blocks
},
matured() {
return this.$store.state.blocks?.matured || []
},
immature() {
return this.$store.state.blocks?.immature || []
},
candidates() {
return this.$store.state.blocks?.candidates || []
},
config() {
return this.$store.state.env
},
locale() {
return this.$i18n.locale
},
},
}
</script>

109
new-web/pages/help.vue

@ -0,0 +1,109 @@
<template>
<v-row justify="center" align="center">
<v-col cols="12" sm="12" md="12">
<v-row no-gutters class="px-4">
<v-alert type="info" class="w-100 mb-0">
Change the address in the examples below to YOUR address before
starting your miner.
</v-alert>
</v-row>
<v-row no-gutters class="px-4">
<v-col cols="12" sm="12" md="12">
<v-card
v-for="(miner, index) in miners"
:key="index"
tile
class="my-2"
>
<v-card-title class="headline ma-0">
{{ miner.title }}
<v-spacer />
<a class="pa-0" :href="miner.releases" target="_blank">
<v-btn color="primary" label>
{{ miner.minVer }}+
<v-icon class="ml-2" small>mdi-download</v-icon>
</v-btn>
</a>
</v-card-title>
<v-card-text class="pa-0">
<article>
<nuxt-content
:document="miner"
:class="{ 'code-lightmode': !darkmode }"
/>
</article>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
</template>
<script>
// import config here as 'this' context within asyncData is not the 'this' we are looking for
import config from '~/params/config.json'
export default {
async asyncData({ $content }) {
const network = config.network
const pathPrefix = 'help/miners/' + network
const miners = []
const supportsClassic = [
'lolminer',
'nanominer',
'trex',
'nbminer',
'gminer',
'teamred',
'srbminer',
]
const supportsMordor = ['lolminer', 'gminer']
if (network === 'mordor') {
for (const miner of supportsMordor) {
const doc = await $content(pathPrefix + '/' + miner).fetch()
miners.push(doc)
}
} else {
for (const miner of supportsClassic) {
const doc = await $content(pathPrefix + '/' + miner).fetch()
miners.push(doc)
}
}
// shuffle miners array to avoid an ordering bias
let currentIndex = miners.length
let temporaryValue
let randomIndex
// While there remain elements to shuffle...
while (currentIndex !== 0) {
// Pick a remaining element...
randomIndex = Math.floor(Math.random() * currentIndex)
currentIndex -= 1
// And swap it with the current element.
temporaryValue = miners[currentIndex]
miners[currentIndex] = miners[randomIndex]
miners[randomIndex] = temporaryValue
}
return {
miners,
}
},
computed: {
darkmode() {
return this.$vuetify.theme.dark
},
},
}
</script>
<style lang="scss" scoped>
.v-card__title {
background-color: var(--v-secondary-base) !important;
}
</style>
>

200
new-web/pages/index.vue

@ -0,0 +1,200 @@
<template>
<v-row justify="center" align="center" no-gutters class="pa-0">
<v-col cols="12" class="pa-0">
<v-card flat tile class="mb-0">
<v-img
height="200"
:src="require('~/static/' + config.banner)"
gradient="to top right, rgba(0,0,0,.9), rgba(255,255,201,.33)"
class="white--text align-end"
>
<v-card-title>
<v-list style="background-color: rgba(0, 0, 0, 0)">
<v-list-item style="background-color: rgba(0, 0, 0, 0)">
<v-list-item-avatar>
<img :src="require('~/static/' + config.logo)" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title class="white--text">{{
config.title
}}</v-list-item-title>
<v-list-item-subtitle class="white--text">{{
config.description
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-title>
</v-img>
<v-alert
v-if="network.testnet"
outlined
text
dismissible
tile
type="warning"
class="w-100 mb-0"
>
{{
$tc('pages.home.testnetAlert', 0, {
title: config.network.title,
symbol: config.network.symbol,
})
}}
</v-alert>
<v-card-text class="py-1">
<v-list style="background-color: rgba(0, 0, 0, 0)">
<v-list-item style="background-color: rgba(0, 0, 0, 0)">
<v-list-item-avatar>
<img :src="require('~/static/' + config.network.icon)" />
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title>{{
config.network.title
}}</v-list-item-title>
<v-list-item-subtitle>{{
config.network.algo
}}</v-list-item-subtitle>
</v-list-item-content>
</v-list-item>
</v-list>
<ul>
<li>
{{
$tc('pages.home.minimumPayout', 0, {
threshold: config.payoutThreshold,
symbol: config.network.symbol,
})
}}
</li>
<li>{{ $tc('pages.home.mode', 0, { mode: config.mode }) }}</li>
<li>
{{ $t('pages.home.poweredBy') }}
<a href="https://github.com/etclabscore/core-pool" target="_blank"
>core-pool</a
>.
</li>
<li>{{ $t('pages.home.protocols') }}</li>
</ul>
</v-card-text>
</v-card>
<v-card flat tile>
<v-card-title>
<v-text-field
v-model="search"
append-icon="mdi-magnify"
:label="$t('pages.home.search')"
single-line
outlined
hide-details
></v-text-field>
</v-card-title>
<v-data-table
dense
flat
:headers="headers"
:items="miners"
:search="search"
:footer-props="{
itemsPerPageText: $t('pages.home.minersPerPage'),
itemsPerPageOptions: [25, 50, 100],
}"
:options="{ itemsPerPage: 25 }"
:items-per-page="-1"
:no-data-text="$t('pages.home.noMiners')"
>
<template #[`item.account`]="{ item }">
<nuxt-link :to="'/account/' + item.account">{{
formatAccountHash(item.account)
}}</nuxt-link>
</template>
<template #[`item.hashrate`]="{ item }">
{{ formatHashrate(item.hashrate, true) }}
</template>
<template #[`item.lastBeat`]="{ item }">
{{ formatLastBeat(item.lastBeat) }}
</template>
</v-data-table>
</v-card>
</v-col>
</v-row>
</template>
<script>
import { formatDistance } from 'date-fns'
export default {
data() {
return {
search: '',
}
},
computed: {
headers() {
return [
{
text: this.$t('pages.home.account'),
align: 'start',
value: 'account',
},
{ text: this.$t('pages.home.hashrate'), value: 'hashrate' },
{
text: this.$t('pages.home.lastBeat'),
align: 'right',
value: 'lastBeat',
},
]
},
miners() {
const obj = this.$store.state.miners
const arr = []
for (const miner in obj) {
arr.push({
account: miner,
hashrate: obj[miner].hr,
lastBeat: obj[miner].lastBeat * 1000,
offline: obj[miner.offline],
})
}
return arr
},
config() {
return this.$store.state.env
},
network() {
return this.$store.state.env.network
},
now() {
return this.$store.state.now
},
},
methods: {
formatHashrate(bytes, showHash) {
const sizes = ['', 'K', 'M', 'G', 'T']
if (bytes === 0) {
return 'n/a'
}
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)))
if (i === 0) {
return bytes + ' ' + sizes[i]
}
let unit = ' ' + sizes[i]
if (showHash) {
unit = ' ' + sizes[i] + 'H'
}
return (bytes / 1000 ** i).toFixed(3) + unit
},
formatAccountHash(account) {
const start = account.substring(0, 10)
const end = account.substring(account.length - 10)
return start + '...' + end
},
formatLastBeat(time) {
return formatDistance(new Date(time), this.now, {
addSuffix: true,
includeSeconds: true,
})
},
},
}
</script>

47
new-web/pages/payments.vue

@ -0,0 +1,47 @@
<template>
<v-row justify="center" align="center" no-gutters>
<v-col cols="12" class="pa-0">
<v-card tile flat>
<v-tabs v-model="tab" background-color="transparent">
<v-tab>{{ $t('pages.payments.latestPayments') }}</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item>
<payments-table
:payments="payments"
:config="config"
:no-data-text="$t('pages.payments.noPayments')"
/>
</v-tab-item>
</v-tabs-items>
</v-card>
</v-col>
</v-row>
</template>
<script>
import PaymentsTable from '~/components/tables/Payments'
export default {
components: {
PaymentsTable,
},
data() {
return {
tab: null,
nf: new Intl.NumberFormat(this.locale, {}),
}
},
computed: {
payments() {
return this.$store.state.payments?.payments
},
config() {
return this.$store.state.env
},
locale() {
return this.$i18n.locale
},
},
}
</script>

93
new-web/params/README.md

@ -0,0 +1,93 @@
## config.json
Copy ~/params/example.config.json to ~/params/config.json
### example config.json
```javascript
{
"title": "core-pool",
"description": "vue based frontend for core-pool",
"logo": "etc.svg",
"favicon": "favicon.png",
"url": "http://127.0.0.1:3000",
"api": "http://127.0.0.1:8080",
"stratum": "127.0.0.1:8008",
"network": "classic",
"explorer": {
"url": "https://blockscout.com",
"type": "blockscout"
},
"poolFee": "1",
"payoutThreshold": "0.5",
"theme": {
"dark": true,
"themes": {
"dark": {
"primary": "#1976D2",
"secondary": "#424242",
"accent": "#82B1FF",
"error": "#FF5252",
"info": "#2196F3",
"success": "#4CAF50",
"warning": "#FFC107",
"borders": "#2E2E2E"
},
"light": {
"primary": "#1976D2",
"secondary": "#F5F5F5",
"accent": "#82B1FF",
"error": "#FF5252",
"info": "#2196F3",
"success": "#4CAF50",
"warning": "#FFC107",
"borders": "#E1E1E1"
}
},
"options": {
"customProperties": true
}
},
"i18n": {
"default": "en",
"fallback": "en"
},
"extraPools": []
}
```
## images/icons
To avoid future merge conflicts dont replace the existing icon/image files, add new ones alongside and update the config. Images/icons can be found in the ~/static directory.
## network
`classic`, `mordor`, `ethereum` or `ubiq`
blocktimes, epochLength, icon, title, algo are set based on network, these values can be found in ~/params/networks.json
## explorer type
`expedition`, `blockscout`, `etherscan` or `spectrum`
## theme
`dark`: If true pool interface defaults to darkmode.
Colors for each theme (dark/light) can be configured via config.json, see: https://vuetifyjs.com/en/features/theme for additional options.
Addtional customizations/overrides can be done via ~/assets/variables.scss, see: https://vuetifyjs.com/en/features/sass-variables/ for more info.
## extraPools
Custom menu links to additional pool instances can easily be configured via the config, simply define any additional pools as follows
```
"extraPools": [
{ "network": "ethereum", "url": "https://ethereum.pool.octano.dev", "type": "PROP" },
{ "network": "ubiq", "url": "https://ubiq.pool.octano.dev", "type": "PPLNS" },
{ "network": "classic", "url": "https://classic.pool.octano.dev", "type": "SOLO" }
]
```
Network must be a supported support key. Define any new networks in ~/params/networks.json (and submit a PR).

51
new-web/params/example.config.json

@ -0,0 +1,51 @@
{
"title": "core-pool",
"description": "vue based frontend for core-pool",
"logo": "etc.svg",
"favicon": "favicon.png",
"banner": "banner.jpg",
"url": "http://127.0.0.1:3000",
"api": "http://127.0.0.1:8080",
"stratum": "127.0.0.1:8008",
"network": "classic",
"explorer": {
"url": "https://blockscout.com",
"type": "blockscout"
},
"poolFee": "1",
"payoutThreshold": "0.5",
"mode": "PROP",
"theme": {
"dark": true,
"themes": {
"dark": {
"primary": "#1976D2",
"secondary": "#424242",
"accent": "#82B1FF",
"error": "#FF5252",
"info": "#2196F3",
"success": "#4CAF50",
"warning": "#FFC107",
"borders": "#2E2E2E"
},
"light": {
"primary": "#1976D2",
"secondary": "#F5F5F5",
"accent": "#82B1FF",
"error": "#FF5252",
"info": "#2196F3",
"success": "#4CAF50",
"warning": "#FFC107",
"borders": "#E1E1E1"
}
},
"options": {
"customProperties": true
}
},
"i18n": {
"default": "en",
"fallback": "en"
},
"extraPools": []
}

35
new-web/params/networks.json

@ -0,0 +1,35 @@
{
"classic": {
"title": "Ethereum Classic",
"symbol": "ETC",
"icon": "etc.svg",
"blockTime": 13.2,
"epochLength": 60000,
"algo": "etchash"
},
"mordor": {
"title": "Mordor Testnet",
"symbol": "ETC",
"icon": "etc.svg",
"blockTime": 13.2,
"epochLength": 60000,
"algo": "etchash",
"testnet": true
},
"ethereum": {
"title": "Ethereum Mainnet",
"symbol": "ETH",
"icon": "eth.png",
"blockTime": 13.2,
"epochLength": 30000,
"algo": "ethash"
},
"ubiq": {
"title": "Ubiq Mainnet",
"symbol": "UBQ",
"icon": "ubq.svg",
"blockTime": 88,
"epochLength": 30000,
"algo": "ubqhash"
}
}

7
new-web/plugins/README.md

@ -0,0 +1,7 @@
# PLUGINS
**This directory is not required, you can delete it if you don't want to use it.**
This directory contains Javascript plugins that you want to run before mounting the root Vue.js application.
More information about the usage of this directory in [the documentation](https://nuxtjs.org/guide/plugins).

BIN
new-web/screenshots/01.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 100 KiB

BIN
new-web/screenshots/02.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 KiB

62
new-web/scss/_custom.scss

@ -0,0 +1,62 @@
@import url('https://fonts.googleapis.com/css?family=Ubuntu+Mono&display=swap');
a {
text-decoration: none;
}
.v-application {
font-family: Ubuntu Mono, monospace !important;
font-style: normal;
font-variant: normal;
}
.w-100 {
width: 100% !important;
}
.h-100 {
height: calc(100vh - 80px)
}
.bg-transparent {
background-color: $transparent !important;
}
.bb-1 {
border-bottom: 1px solid $grey3 !important;
}
.nuxt-content-highlight pre {
background-color: $transparent !important;
border: none;
}
.nuxt-content-highlight pre code {
background-color:$transparent !important;
text-shadow: none !important;
}
.theme--dark.v-application {
background-color: #1E1E1E !important;
}
// scrollbars
::-webkit-scrollbar {
width: 6px; /* for vertical scrollbars */
height: 6px; /* for horizontal scrollbars */
}
::-webkit-scrollbar-track {
background: var(--v-borders-base) !important;
z-index: 4 !important
}
::-webkit-scrollbar-thumb {
background: var(--v-primary-base) !important;
}
// firefox scrollbar z-index fix
.ff-scrollbar-fix {
transform: translate3d(0, 0, 0);
scrollbar-color: var(--v-primary-base) var(--v-borders-base);
scrollbar-width: thin;
}

11
new-web/scss/main.scss

@ -0,0 +1,11 @@
$grey1: #111 !default;
$grey2: #222 !default;
$grey3: #333 !default;
$grey4: #444 !default;
$grey5: #555 !default;
$greyLight: #e6e6e6 !default;
$transparent: rgba(0,0,0,0);
@import 'custom';

BIN
new-web/static/banner.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

1
new-web/static/etc.svg

@ -0,0 +1 @@
<svg enable-background="new 0 0 133.7 220.5" viewBox="0 0 133.7 220.5" xmlns="http://www.w3.org/2000/svg"><g fill="#33ff99"><path d="m2.4 98.8 65-27.4 63 28.1-63.1-99.5zm.2 30.4 64.9 37.6 66.2-37.6-65.6 91.3z"/><path d="m67.7 84.8-67.7 28.5 67.7 37.6 65.8-36.8z"/></g></svg>

After

Width:  |  Height:  |  Size: 274 B

BIN
new-web/static/eth.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

BIN
new-web/static/favicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

1
new-web/static/ubq.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1785 2000" width="2500" height="2500"><path d="M1089.3 213l25.24 804.73L1785 594.08z" fill="#333"/><path d="M1780.27 1436.69V590.93L819.54 1200v800h32.34" fill="#494949"/><path d="M695.7 1787l-25.24-804.75L0 1405.92z" fill="#0ca579"/><path d="M4.73 563.31v845.76l960.73-608.28V0h-32.34" fill="#00ea90"/></svg>

After

Width:  |  Height:  |  Size: 362 B

122
new-web/store/index.js

@ -0,0 +1,122 @@
import axios from 'axios'
import consola from 'consola'
import config from '@/params/config.json'
import networks from '@/params/networks.json'
const TARGET_TIME = networks[config.network].blockTime
const EPOCH_LENGTH = networks[config.network].epochLength
const API_URL = config.api + '/api'
export const state = () => ({
env: {
title: config.title,
description: config.description,
logo: config.logo,
favicon: config.favicon,
banner: config.banner,
url: config.url,
api: API_URL,
network: networks[config.network],
stratum: config.stratum,
symbol: networks[config.network].symbol,
explorer: config.explorer,
poolFee: config.poolFee,
payoutThreshold: config.payoutThreshold,
extraPools: config.extraPools,
mode: config.mode,
},
networks,
minersOnline: 0,
poolHashRate: 0,
lastBlockFound: 0,
roundShares: 0,
height: 0,
difficulty: 0,
networkHashrate: 0,
miners: {},
blocks: {},
payments: {},
epoch: 0,
dagSize: 0, // in MB
now: Date.now(), // global now Date for time since calcs
})
export const mutations = {
SET_STATS(state, info) {
state.minersOnline = info.minersOnline | state.minersOnline
state.poolHashRate = info.poolHashRate | state.poolHashRate
state.lastBlockFound = info.lastBlockFound | state.lastBlockFound
state.roundShares = info.roundShares | state.roundShares
state.poolFee = info.poolFee | state.poolFee
state.height = info.height | state.height
state.difficulty = info.difficulty | state.difficulty
state.networkHashrate = state.difficulty / TARGET_TIME
state.epoch = Math.trunc(info.height / EPOCH_LENGTH)
state.dagSize = 1024 + 8 * state.epoch
},
SET_MINERS(state, miners) {
state.miners = miners
},
SET_BLOCKS(state, blocks) {
state.blocks = blocks
},
SET_PAYMENTS(state, txns) {
state.payments = txns
},
SET_NOW(state, now) {
state.now = now
},
}
export const actions = {
async stats({ commit }) {
try {
const { data } = await axios.get(API_URL + '/stats')
if (data) {
const info = {
minersOnline: data.minersTotal,
poolHashRate: data.hashrate,
height: data.nodes[0].height,
difficulty: data.nodes[0].difficulty,
lastBlockFound: data.stats.lastBlockFound,
}
commit('SET_STATS', info)
}
} catch (error) {
consola.error(new Error(error))
}
},
async miners({ commit }) {
try {
const { data } = await axios.get(API_URL + '/miners')
if (data) {
commit('SET_MINERS', data.miners)
}
} catch (error) {
consola.error(new Error(error))
}
},
async blocks({ commit }) {
try {
const { data } = await axios.get(API_URL + '/blocks')
if (data) {
commit('SET_BLOCKS', data)
}
} catch (error) {
consola.error(new Error(error))
}
},
async payments({ commit }) {
try {
const { data } = await axios.get(API_URL + '/payments')
if (data) {
commit('SET_PAYMENTS', data)
}
} catch (error) {
consola.error(new Error(error))
}
},
now({ commit }) {
commit('SET_NOW', Date.now())
},
}

39
new-web/test/ExplorerLink.spec.js

@ -0,0 +1,39 @@
// Libraries
import Vue from 'vue'
import Vuetify from 'vuetify'
// components
import ExplorerLink from '@/components/ExplorerLink.vue'
import { mount, createLocalVue } from '@vue/test-utils'
Vue.use(Vuetify)
const localVue = createLocalVue()
describe('ExplorerLink.vue', () => {
let vuetify
beforeEach(() => {
vuetify = new Vuetify()
})
it('is a Vue instance', () => {
const wrapper = mount(ExplorerLink, {
localVue,
vuetify,
propsData: {
config: {
network: 'classic',
explorer: {
url: 'https://blockscout.com',
type: 'blockscout',
},
symbol: 'ETC',
},
},
})
expect(wrapper.vm).toBeTruthy()
})
})

12438
new-web/yarn.lock

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save