|
@ -1 +1,2 @@
|
|||
/dist
|
||||
/src/validators/address_tools.js
|
||||
|
|
|
@ -25,6 +25,9 @@ module.exports = {
|
|||
},
|
||||
// add your custom rules here
|
||||
"rules": {
|
||||
"indent": ["error", 4],
|
||||
"quotes": ["error", "double"],
|
||||
|
||||
// allow async-await
|
||||
"generator-star-spacing": "off",
|
||||
|
||||
|
@ -32,6 +35,8 @@ module.exports = {
|
|||
"arrow-parens": 0,
|
||||
"one-var": 0,
|
||||
|
||||
"camelcase": 0,
|
||||
|
||||
"import/first": 0,
|
||||
"import/named": 2,
|
||||
"import/namespace": 2,
|
||||
|
|
13
README.md
|
@ -4,12 +4,16 @@ Loki GUI wallet
|
|||
|
||||
### Building from source
|
||||
|
||||
#### Pre-requisite
|
||||
- Download latest [Lokid](https://github.com/loki-project/loki/releases)
|
||||
|
||||
#### Commands
|
||||
```
|
||||
npm install -g quasar-cli
|
||||
git clone https://github.com/ryo-currency/ryo-wallet
|
||||
cd ryo-wallet
|
||||
cp /path/to/ryo/binaries/ryod bin/
|
||||
cp /path/to/ryo/binaries/ryo-wallet-rpc bin/
|
||||
git clone https://github.com/loki-project/loki-electron-wallet
|
||||
cd loki-electron-wallet
|
||||
cp path_to_loki_binaries/lokid bin/
|
||||
cp path_to_loki_binaries/loki-wallet-rpc bin/
|
||||
npm install
|
||||
quasar build -m electron -t mat
|
||||
```
|
||||
|
@ -18,6 +22,7 @@ quasar build -m electron -t mat
|
|||
|
||||
### LICENSE
|
||||
|
||||
Copyright (c) 2018-2019, Loki Project
|
||||
Copyright (c) 2018, Ryo Currency Project
|
||||
|
||||
Portions of this software are available under BSD-3 license. Please see ORIGINAL-LICENSE for details
|
||||
|
|
12958
package-lock.json
generated
21
package.json
|
@ -1,19 +1,23 @@
|
|||
{
|
||||
"name": "ryo-wallet-atom",
|
||||
"name": "loki-wallet-atom",
|
||||
"version": "1.1.0",
|
||||
"daemonVersion": "0.3.2.0",
|
||||
"description": "Modern GUI interface for Ryo Currency",
|
||||
"productName": "Ryo Wallet Atom",
|
||||
"cordovaId": "com.ryo-currency.ryo-gui-wallet",
|
||||
"author": "Ryo-currency <contact@ryo-currency.com>",
|
||||
"description": "Modern GUI interface for Loki Currency",
|
||||
"productName": "Loki Wallet Atom",
|
||||
"cordovaId": "com.lokinetwork.wallet",
|
||||
"author": "Loki",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "quasar dev -m electron -t mat",
|
||||
"build": "quasar build -m electron -t mat",
|
||||
"lint": "eslint --ext .js,.vue src",
|
||||
"lint-fix": "eslint --fix .",
|
||||
"test": "echo \"No test specified\" && exit 0"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "^0.18.0",
|
||||
"electron-window-state": "^5.0.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"object-assign-deep": "^0.4.0",
|
||||
"portscanner": "^2.2.0",
|
||||
"promise-queue": "^2.2.5",
|
||||
|
@ -41,9 +45,10 @@
|
|||
"eslint-plugin-promise": "^3.7.0",
|
||||
"eslint-plugin-standard": "^3.0.1",
|
||||
"eslint-plugin-vue": "^4.3.0",
|
||||
"node-sass": "^4.9.3",
|
||||
"quasar-cli": "^0.16.0",
|
||||
"sass-loader": "^7.1.0"
|
||||
"node-sass": "^4.11.0",
|
||||
"quasar-cli": "^0.17.24",
|
||||
"sass-loader": "^7.1.0",
|
||||
"strip-ansi": "^3.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8.9.0",
|
||||
|
|
|
@ -6,7 +6,7 @@ module.exports = function (ctx) {
|
|||
plugins: [
|
||||
"i18n",
|
||||
"axios",
|
||||
"vuelidate",
|
||||
"vuelidate",
|
||||
"gateway",
|
||||
"timeago"
|
||||
],
|
||||
|
@ -28,7 +28,7 @@ module.exports = function (ctx) {
|
|||
// gzip: true,
|
||||
// analyze: true,
|
||||
// extractCSS: false,
|
||||
extendWebpack(cfg) {
|
||||
extendWebpack (cfg) {
|
||||
/*
|
||||
cfg.module.rules.push({
|
||||
enforce: "pre",
|
||||
|
@ -59,6 +59,7 @@ module.exports = function (ctx) {
|
|||
"QField",
|
||||
"QInput",
|
||||
"QRadio",
|
||||
"QOptionGroup",
|
||||
"QBtn",
|
||||
"QBtnToggle",
|
||||
"QIcon",
|
||||
|
@ -88,7 +89,8 @@ module.exports = function (ctx) {
|
|||
"QInnerLoading",
|
||||
"QInfiniteScroll",
|
||||
"QDatetime",
|
||||
"QContextMenu"
|
||||
"QContextMenu",
|
||||
"QScrollArea"
|
||||
],
|
||||
directives: [
|
||||
"Ripple",
|
||||
|
@ -116,32 +118,32 @@ module.exports = function (ctx) {
|
|||
display: "standalone",
|
||||
orientation: "portrait",
|
||||
background_color: "#ffffff",
|
||||
theme_color: "#027be3",
|
||||
theme_color: "#43BD43",
|
||||
icons: [{
|
||||
"src": "statics/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
"src": "statics/icons/icon-128x128.png",
|
||||
"sizes": "128x128",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-256x256.png",
|
||||
"sizes": "256x256",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-384x384.png",
|
||||
"sizes": "384x384",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "statics/icons/icon-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
|
@ -150,7 +152,7 @@ module.exports = function (ctx) {
|
|||
},
|
||||
electron: {
|
||||
bundler: "builder", // or "packager"
|
||||
extendWebpack(cfg) {
|
||||
extendWebpack (cfg) {
|
||||
// do something with Electron process Webpack cfg
|
||||
},
|
||||
packager: {
|
||||
|
@ -166,15 +168,15 @@ module.exports = function (ctx) {
|
|||
// win32metadata: { ... }
|
||||
|
||||
extraResource: [
|
||||
"bin",
|
||||
"bin"
|
||||
]
|
||||
},
|
||||
builder: {
|
||||
// https://www.electron.build/configuration/configuration
|
||||
|
||||
appId: "com.ryo-currency.wallet",
|
||||
productName: "Ryo Wallet Atom",
|
||||
copyright: "Copyright © 2018 Ryo Currency Project",
|
||||
appId: "com.lokinetwork.wallet",
|
||||
productName: "Loki Wallet Atom",
|
||||
copyright: "Copyright © 2018-2019 Loki Project, 2018 Ryo Currency Project",
|
||||
|
||||
// directories: {
|
||||
// buildResources: "src-electron/build"
|
||||
|
|
Before Width: | Height: | Size: 20 KiB After Width: | Height: | Size: 114 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 51 KiB |
|
@ -19,30 +19,30 @@ let mainWindow, backend
|
|||
let showConfirmClose = true
|
||||
let forceQuit = false
|
||||
|
||||
const portInUse = function(port, callback) {
|
||||
var server = net.createServer(function(socket) {
|
||||
socket.write("Echo server\r\n");
|
||||
socket.pipe(socket);
|
||||
});
|
||||
const portInUse = function (port, callback) {
|
||||
var server = net.createServer(function (socket) {
|
||||
socket.write("Echo server\r\n")
|
||||
socket.pipe(socket)
|
||||
})
|
||||
|
||||
server.listen(port, "127.0.0.1");
|
||||
server.listen(port, "127.0.0.1")
|
||||
server.on("error", function (e) {
|
||||
callback(true);
|
||||
});
|
||||
callback(true)
|
||||
})
|
||||
server.on("listening", function (e) {
|
||||
server.close();
|
||||
callback(false);
|
||||
});
|
||||
};
|
||||
server.close()
|
||||
callback(false)
|
||||
})
|
||||
}
|
||||
|
||||
function createWindow() {
|
||||
function createWindow () {
|
||||
/**
|
||||
* Initial window options
|
||||
*/
|
||||
|
||||
let mainWindowState = windowStateKeeper({
|
||||
defaultWidth: 800,
|
||||
defaultHeight: 650
|
||||
defaultWidth: 900,
|
||||
defaultHeight: 700
|
||||
})
|
||||
|
||||
mainWindow = new BrowserWindow({
|
||||
|
@ -93,11 +93,9 @@ function createWindow() {
|
|||
})
|
||||
|
||||
mainWindow.webContents.on("did-finish-load", () => {
|
||||
|
||||
require("crypto").randomBytes(64, (err, buffer) => {
|
||||
|
||||
// if err, then we may have to use insecure token generation perhaps
|
||||
if (err) throw err;
|
||||
if (err) throw err
|
||||
|
||||
let config = {
|
||||
port: 12213,
|
||||
|
@ -112,20 +110,16 @@ function createWindow() {
|
|||
} else {
|
||||
dialog.showMessageBox(mainWindow, {
|
||||
title: "Startup error",
|
||||
message: `Ryo Wallet is already open, or port ${config.port} is in use`,
|
||||
message: `Loki Wallet is already open, or port ${config.port} is in use`,
|
||||
type: "error",
|
||||
buttons: ["ok"]
|
||||
}, () => {
|
||||
showConfirmClose = false
|
||||
app.quit()
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
})
|
||||
|
||||
mainWindow.loadURL(process.env.APP_URL)
|
||||
|
|
|
@ -35,7 +35,7 @@ let template = [
|
|||
submenu: [
|
||||
{
|
||||
label: "Learn More",
|
||||
click () { require("electron").shell.openExternal("https://ryo-currency.com/") }
|
||||
click () { require("electron").shell.openExternal("https://loki.network/") }
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -43,7 +43,7 @@ let template = [
|
|||
|
||||
if (process.platform === "darwin") {
|
||||
template.unshift({
|
||||
label: "Ryo Wallet Atom",
|
||||
label: "Loki Wallet Atom",
|
||||
submenu: [
|
||||
{role: "about"},
|
||||
{type: "separator"},
|
||||
|
|
|
@ -22,70 +22,70 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const crypto = require("crypto");
|
||||
const crypto = require("crypto")
|
||||
|
||||
const ALGORITHM_NAME = "aes-128-gcm";
|
||||
const ALGORITHM_NONCE_SIZE = 12;
|
||||
const ALGORITHM_TAG_SIZE = 16;
|
||||
const ALGORITHM_KEY_SIZE = 16;
|
||||
const PBKDF2_NAME = "sha256";
|
||||
const PBKDF2_SALT_SIZE = 16;
|
||||
const PBKDF2_ITERATIONS = 32767;
|
||||
const ALGORITHM_NAME = "aes-128-gcm"
|
||||
const ALGORITHM_NONCE_SIZE = 12
|
||||
const ALGORITHM_TAG_SIZE = 16
|
||||
const ALGORITHM_KEY_SIZE = 16
|
||||
const PBKDF2_NAME = "sha256"
|
||||
const PBKDF2_SALT_SIZE = 16
|
||||
const PBKDF2_ITERATIONS = 32767
|
||||
|
||||
export class SCEE {
|
||||
encryptString(plaintext, password) {
|
||||
encryptString (plaintext, password) {
|
||||
// Generate a 128-bit salt using a CSPRNG.
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Encrypt and prepend salt.
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ]);
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ])
|
||||
|
||||
// Return as base64 string.
|
||||
return ciphertextAndNonceAndSalt.toString("base64");
|
||||
return ciphertextAndNonceAndSalt.toString("base64")
|
||||
}
|
||||
|
||||
decryptString(base64CiphertextAndNonceAndSalt, password) {
|
||||
decryptString (base64CiphertextAndNonceAndSalt, password) {
|
||||
// Decode the base64.
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64");
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64")
|
||||
|
||||
// Create buffers of salt and ciphertextAndNonce.
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE)
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Decrypt and return result.
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8");
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8")
|
||||
}
|
||||
|
||||
encrypt(plaintext, key) {
|
||||
encrypt (plaintext, key) {
|
||||
// Generate a 96-bit nonce using a CSPRNG.
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Encrypt and prepend nonce.
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ])
|
||||
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ]);
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ])
|
||||
}
|
||||
|
||||
decrypt(ciphertextAndNonce, key) {
|
||||
decrypt (ciphertextAndNonce, key) {
|
||||
// Create buffers of nonce, ciphertext and tag.
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE)
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE)
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Decrypt and return result.
|
||||
cipher.setAuthTag(tag);
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ]);
|
||||
cipher.setAuthTag(tag)
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,79 +1,126 @@
|
|||
import { Daemon } from "./daemon";
|
||||
import { WalletRPC } from "./wallet-rpc";
|
||||
import { SCEE } from "./SCEE-Node";
|
||||
import { dialog } from "electron";
|
||||
import { Daemon } from "./daemon"
|
||||
import { WalletRPC } from "./wallet-rpc"
|
||||
import { SCEE } from "./SCEE-Node"
|
||||
import { dialog } from "electron"
|
||||
|
||||
const WebSocket = require("ws");
|
||||
const os = require("os");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
const WebSocket = require("ws")
|
||||
const os = require("os")
|
||||
const fs = require("fs-extra")
|
||||
const path = require("path")
|
||||
const objectAssignDeep = require("object-assign-deep")
|
||||
|
||||
export class Backend {
|
||||
constructor(mainWindow) {
|
||||
constructor (mainWindow) {
|
||||
this.mainWindow = mainWindow
|
||||
this.daemon = null
|
||||
this.walletd = null
|
||||
this.wss = null
|
||||
this.token = null
|
||||
this.config_dir = null
|
||||
this.wallet_dir = null
|
||||
this.config_file = null
|
||||
this.config_data = {}
|
||||
this.scee = new SCEE()
|
||||
}
|
||||
|
||||
init(config) {
|
||||
|
||||
if(os.platform() == "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\ryo";
|
||||
//this.config_dir = path.join(os.homedir(), "ryo");
|
||||
init (config) {
|
||||
if (os.platform() === "win32") {
|
||||
this.config_dir = "C:\\ProgramData\\loki"
|
||||
this.wallet_dir = `${os.homedir()}\\Documents\\Loki`
|
||||
} else {
|
||||
this.config_dir = path.join(os.homedir(), ".ryo");
|
||||
this.config_dir = path.join(os.homedir(), ".loki")
|
||||
this.wallet_dir = path.join(os.homedir(), "Loki")
|
||||
}
|
||||
|
||||
if (!fs.existsSync(this.config_dir)) {
|
||||
fs.mkdirSync(this.config_dir);
|
||||
fs.mkdirpSync(this.config_dir)
|
||||
}
|
||||
|
||||
if (!fs.existsSync(path.join(this.config_dir, "gui"))) {
|
||||
fs.mkdirSync(path.join(this.config_dir, "gui"));
|
||||
if (!fs.existsSync(path.join(this.config_dir, "gui"))) {
|
||||
fs.mkdirpSync(path.join(this.config_dir, "gui"))
|
||||
}
|
||||
|
||||
this.config_file = path.join(this.config_dir, "gui", "config.json");
|
||||
this.config_file = path.join(this.config_dir, "gui", "config.json")
|
||||
|
||||
this.config_data = {
|
||||
const daemon = {
|
||||
type: "remote",
|
||||
p2p_bind_ip: "0.0.0.0",
|
||||
p2p_bind_port: 22022,
|
||||
rpc_bind_ip: "127.0.0.1",
|
||||
rpc_bind_port: 22023,
|
||||
zmq_rpc_bind_ip: "127.0.0.1",
|
||||
zmq_rpc_bind_port: 22024,
|
||||
out_peers: -1,
|
||||
in_peers: -1,
|
||||
limit_rate_up: -1,
|
||||
limit_rate_down: -1,
|
||||
log_level: 0
|
||||
}
|
||||
|
||||
const daemons = {
|
||||
mainnet: {
|
||||
...daemon,
|
||||
remote_host: "doopool.xyz",
|
||||
remote_port: 22020
|
||||
},
|
||||
stagenet: {
|
||||
...daemon,
|
||||
type: "local",
|
||||
p2p_bind_port: 38153,
|
||||
rpc_bind_port: 38154,
|
||||
zmq_rpc_bind_port: 38155
|
||||
},
|
||||
testnet: {
|
||||
...daemon,
|
||||
type: "local",
|
||||
p2p_bind_port: 38156,
|
||||
rpc_bind_port: 38157,
|
||||
zmq_rpc_bind_port: 38158
|
||||
}
|
||||
}
|
||||
|
||||
// Default values
|
||||
this.defaults = {
|
||||
daemons: objectAssignDeep({}, daemons),
|
||||
app: {
|
||||
data_dir: this.config_dir,
|
||||
wallet_data_dir: this.wallet_dir,
|
||||
ws_bind_port: 12213,
|
||||
testnet: false
|
||||
net_type: "mainnet"
|
||||
},
|
||||
|
||||
appearance: {
|
||||
theme: "light"
|
||||
},
|
||||
|
||||
daemon: {
|
||||
type: "local_remote",
|
||||
remote_host: "geo.ryoblocks.com",
|
||||
remote_port: 12211,
|
||||
p2p_bind_ip: "0.0.0.0",
|
||||
p2p_bind_port: 12210,
|
||||
rpc_bind_ip: "127.0.0.1",
|
||||
rpc_bind_port: 12211,
|
||||
zmq_rpc_bind_ip: "127.0.0.1",
|
||||
zmq_rpc_bind_port: 12212,
|
||||
out_peers: -1,
|
||||
in_peers: -1,
|
||||
limit_rate_up: -1,
|
||||
limit_rate_down: -1,
|
||||
log_level: 0
|
||||
},
|
||||
|
||||
wallet: {
|
||||
rpc_bind_port: 12214,
|
||||
rpc_bind_port: 18082,
|
||||
log_level: 0
|
||||
}
|
||||
}
|
||||
|
||||
this.config_data = {
|
||||
// Copy all the properties of defaults
|
||||
...objectAssignDeep({}, this.defaults),
|
||||
appearance: {
|
||||
theme: "dark"
|
||||
}
|
||||
}
|
||||
|
||||
this.remotes = [
|
||||
{
|
||||
host: "doopool.xyz",
|
||||
port: "22020"
|
||||
},
|
||||
{
|
||||
host: "daemons.cryptopool.space",
|
||||
port: "22023"
|
||||
},
|
||||
{
|
||||
host: "node.loki-pool.com",
|
||||
port: "18081"
|
||||
},
|
||||
{
|
||||
host: "imaginary.stream",
|
||||
port: "22023"
|
||||
}
|
||||
]
|
||||
|
||||
this.token = config.token
|
||||
|
||||
this.wss = new WebSocket.Server({
|
||||
|
@ -82,284 +129,413 @@ export class Backend {
|
|||
})
|
||||
|
||||
this.wss.on("connection", ws => {
|
||||
ws.on("message", data => this.receive(data));
|
||||
});
|
||||
|
||||
ws.on("message", data => this.receive(data))
|
||||
})
|
||||
}
|
||||
|
||||
send(event, data={}) {
|
||||
send (event, data = {}) {
|
||||
let message = {
|
||||
event,
|
||||
data
|
||||
}
|
||||
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token);
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token)
|
||||
|
||||
this.wss.clients.forEach(function each(client) {
|
||||
this.wss.clients.forEach(function each (client) {
|
||||
if (client.readyState === WebSocket.OPEN) {
|
||||
client.send(encrypted_data)
|
||||
}
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
receive(data) {
|
||||
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(data, this.token));
|
||||
receive (data) {
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(data, this.token))
|
||||
|
||||
// route incoming request to either the daemon, wallet, or here
|
||||
switch (decrypted_data.module) {
|
||||
case "core":
|
||||
this.handle(decrypted_data);
|
||||
break;
|
||||
case "daemon":
|
||||
if (this.daemon) {
|
||||
this.daemon.handle(decrypted_data);
|
||||
}
|
||||
break;
|
||||
case "wallet":
|
||||
if (this.walletd) {
|
||||
this.walletd.handle(decrypted_data);
|
||||
}
|
||||
break;
|
||||
case "core":
|
||||
this.handle(decrypted_data)
|
||||
break
|
||||
case "daemon":
|
||||
if (this.daemon) {
|
||||
this.daemon.handle(decrypted_data)
|
||||
}
|
||||
break
|
||||
case "wallet":
|
||||
if (this.walletd) {
|
||||
this.walletd.handle(decrypted_data)
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
handle(data) {
|
||||
|
||||
handle (data) {
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
case "quick_save_config":
|
||||
// save only partial config settings
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
case "quick_save_config":
|
||||
// save only partial config settings
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
})
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
})
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), 'utf8', () => {
|
||||
})
|
||||
break
|
||||
|
||||
case "save_config":
|
||||
// check if config has changed
|
||||
let config_changed = false
|
||||
Object.keys(this.config_data).map(i => {
|
||||
if (i == "appearance") return
|
||||
Object.keys(this.config_data[i]).map(j => {
|
||||
if (this.config_data[i][j] !== params[i][j]) { config_changed = true }
|
||||
})
|
||||
})
|
||||
case "save_config_init":
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
})
|
||||
|
||||
const validated = Object.keys(this.defaults)
|
||||
.filter(k => k in this.config_data)
|
||||
.map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])])
|
||||
.reduce((map, obj) => {
|
||||
map[obj[0]] = obj[1]
|
||||
return map
|
||||
}, {})
|
||||
|
||||
// Validate deamon data
|
||||
this.config_data = {
|
||||
...this.config_data,
|
||||
...validated
|
||||
}
|
||||
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {
|
||||
if (data.method == "save_config_init") {
|
||||
this.startup()
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: params,
|
||||
pending_config: params
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
})
|
||||
break
|
||||
|
||||
case "save_config":
|
||||
// check if config has changed
|
||||
let config_changed = false
|
||||
Object.keys(this.config_data).map(i => {
|
||||
if(i == "appearance") return
|
||||
Object.keys(this.config_data[i]).map(j => {
|
||||
if(this.config_data[i][j] !== params[i][j])
|
||||
config_changed = true
|
||||
})
|
||||
})
|
||||
case "save_config_init":
|
||||
Object.keys(params).map(key => {
|
||||
this.config_data[key] = Object.assign(this.config_data[key], params[key])
|
||||
});
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), 'utf8', () => {
|
||||
|
||||
if(data.method == "save_config_init") {
|
||||
this.startup();
|
||||
} else {
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
})
|
||||
if(config_changed) {
|
||||
this.send("settings_changed_reboot")
|
||||
}
|
||||
if (config_changed) {
|
||||
this.send("settings_changed_reboot")
|
||||
}
|
||||
});
|
||||
break;
|
||||
case "init":
|
||||
this.startup();
|
||||
break;
|
||||
|
||||
case "open_explorer":
|
||||
if(params.type == "tx") {
|
||||
require("electron").shell.openExternal("https://explorer.ryo-currency.com/tx/"+params.id)
|
||||
}
|
||||
break;
|
||||
})
|
||||
break
|
||||
case "init":
|
||||
this.startup()
|
||||
break
|
||||
|
||||
case "open_url":
|
||||
require("electron").shell.openExternal(params.url)
|
||||
break;
|
||||
case "open_explorer":
|
||||
if (params.type == "tx") {
|
||||
require("electron").shell.openExternal("https://lokiblocks.com/tx/" + params.id)
|
||||
}
|
||||
break
|
||||
|
||||
case "save_png":
|
||||
let filename = dialog.showSaveDialog(this.mainWindow, {
|
||||
title: "Save "+params.type,
|
||||
filters: [{name: "PNG", extensions:["png"]}],
|
||||
defaultPath: os.homedir()
|
||||
case "open_url":
|
||||
require("electron").shell.openExternal(params.url)
|
||||
break
|
||||
|
||||
case "save_png":
|
||||
let filename = dialog.showSaveDialog(this.mainWindow, {
|
||||
title: "Save " + params.type,
|
||||
filters: [{name: "PNG", extensions: ["png"]}],
|
||||
defaultPath: os.homedir()
|
||||
})
|
||||
if (filename) {
|
||||
let base64Data = params.img.replace(/^data:image\/png;base64,/, "")
|
||||
let binaryData = new Buffer(base64Data, "base64").toString("binary")
|
||||
fs.writeFile(filename, binaryData, "binary", (err) => {
|
||||
if (err) { this.send("show_notification", {type: "negative", message: "Error saving " + params.type, timeout: 2000}) } else { this.send("show_notification", {message: params.type + " saved to " + filename, timeout: 2000}) }
|
||||
})
|
||||
if(filename) {
|
||||
let base64Data = params.img.replace(/^data:image\/png;base64,/,"")
|
||||
let binaryData = new Buffer(base64Data, 'base64').toString("binary")
|
||||
fs.writeFile(filename, binaryData, "binary", (err) => {
|
||||
if(err)
|
||||
this.send("show_notification", {type: "negative", message: "Error saving "+params.type, timeout: 2000})
|
||||
else
|
||||
this.send("show_notification", {message: params.type+" saved to "+filename, timeout: 2000})
|
||||
})
|
||||
}
|
||||
break;
|
||||
}
|
||||
break
|
||||
|
||||
default:
|
||||
default:
|
||||
}
|
||||
}
|
||||
|
||||
startup() {
|
||||
fs.readFile(this.config_file, "utf8", (err,data) => {
|
||||
startup () {
|
||||
this.send("set_app_data", {
|
||||
remotes: this.remotes,
|
||||
defaults: this.defaults
|
||||
})
|
||||
|
||||
fs.readFile(this.config_file, "utf8", (err, data) => {
|
||||
if (err) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Config not found
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
return;
|
||||
pending_config: this.config_data
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
let disk_config_data = JSON.parse(data);
|
||||
let disk_config_data = JSON.parse(data)
|
||||
|
||||
// semi-shallow object merge
|
||||
Object.keys(disk_config_data).map(key => {
|
||||
if(!this.config_data.hasOwnProperty(key))
|
||||
this.config_data[key] = {}
|
||||
if (!this.config_data.hasOwnProperty(key)) { this.config_data[key] = {} }
|
||||
this.config_data[key] = Object.assign(this.config_data[key], disk_config_data[key])
|
||||
});
|
||||
})
|
||||
|
||||
// here we may want to check if config data is valid, if not also send code -1
|
||||
// i.e. check ports are integers and > 1024, check that data dir path exists, etc
|
||||
const validated = Object.keys(this.defaults)
|
||||
.filter(k => k in this.config_data)
|
||||
.map(k => [k, this.validate_values(this.config_data[k], this.defaults[k])])
|
||||
.reduce((map, obj) => {
|
||||
map[obj[0]] = obj[1]
|
||||
return map
|
||||
}, {})
|
||||
|
||||
// Make sure the daemon data is valid
|
||||
this.config_data = {
|
||||
...this.config_data,
|
||||
...validated
|
||||
}
|
||||
|
||||
// save config file back to file, so updated options are stored on disk
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), 'utf8');
|
||||
fs.writeFile(this.config_file, JSON.stringify(this.config_data, null, 4), "utf8", () => {})
|
||||
|
||||
this.send("set_app_data", {
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
pending_config: this.config_data
|
||||
})
|
||||
|
||||
if(this.config_data.app.testnet) {
|
||||
// Make the wallet dir
|
||||
const { wallet_data_dir, data_dir } = this.config_data.app
|
||||
if (!fs.existsSync(wallet_data_dir)) { fs.mkdirpSync(wallet_data_dir) }
|
||||
|
||||
let testnet_dir = path.join(this.config_data.app.data_dir, "testnet")
|
||||
if (!fs.existsSync(testnet_dir))
|
||||
fs.mkdirSync(testnet_dir);
|
||||
// Check to see if data and wallet directories exist
|
||||
const dirs_to_check = [{
|
||||
path: data_dir,
|
||||
error: "Data storge path not found"
|
||||
},
|
||||
{
|
||||
path: wallet_data_dir,
|
||||
error: "Wallet data storge path not found"
|
||||
}]
|
||||
|
||||
let log_dir = path.join(this.config_data.app.data_dir, "testnet", "logs")
|
||||
if (!fs.existsSync(log_dir))
|
||||
fs.mkdirSync(log_dir);
|
||||
|
||||
} else {
|
||||
|
||||
let log_dir = path.join(this.config_data.app.data_dir, "logs")
|
||||
if (!fs.existsSync(log_dir))
|
||||
fs.mkdirSync(log_dir);
|
||||
for (const dir of dirs_to_check) {
|
||||
// Check to see if dir exists
|
||||
if (!fs.existsSync(dir.path)) {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: `Error: ${dir.error}`,
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
this.daemon = new Daemon(this);
|
||||
this.walletd = new WalletRPC(this);
|
||||
const { net_type } = this.config_data.app
|
||||
|
||||
const dirs = {
|
||||
"mainnet": this.config_data.app.data_dir,
|
||||
"stagenet": path.join(this.config_data.app.data_dir, "stagenet"),
|
||||
"testnet": path.join(this.config_data.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
// Make sure we have the directories we need
|
||||
const net_dir = dirs[net_type]
|
||||
if (!fs.existsSync(net_dir)) { fs.mkdirpSync(net_dir) }
|
||||
|
||||
const log_dir = path.join(net_dir, "logs")
|
||||
if (!fs.existsSync(log_dir)) { fs.mkdirpSync(log_dir) }
|
||||
|
||||
this.daemon = new Daemon(this)
|
||||
this.walletd = new WalletRPC(this)
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 3 // Starting daemon
|
||||
}
|
||||
});
|
||||
|
||||
this.daemon.checkVersion().then((version) => {
|
||||
|
||||
if(version) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 4,
|
||||
message: version
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// daemon not found, probably removed by AV, set to remote node
|
||||
this.config_data.daemon.type = "remote"
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 5
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data,
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
this.daemon.start(this.config_data).then(() => {
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 6 // Starting wallet
|
||||
}
|
||||
});
|
||||
|
||||
this.walletd.start(this.config_data).then(() => {
|
||||
})
|
||||
|
||||
// Make sure the remote node provided is accessible
|
||||
const config_daemon = this.config_data.daemons[net_type]
|
||||
this.daemon.checkRemote(config_daemon).then(data => {
|
||||
if (data.error) {
|
||||
// If we can default to local then we do so, otherwise we tell the user to re-set the node
|
||||
if (config_daemon.type === "local_remote") {
|
||||
this.config_data.daemons[net_type].type = "local"
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 7 // Reading wallet list
|
||||
}
|
||||
});
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
this.send("show_notification", {
|
||||
type: "warning",
|
||||
textColor: "black",
|
||||
message: "Warning: Could not access remote node, switching to local only",
|
||||
timeout: 2000
|
||||
})
|
||||
} else {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: "Error: Could not access remote node, please try another remote node",
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
this.walletd.listWallets(true)
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 0 // Ready
|
||||
}
|
||||
});
|
||||
}).catch(error => {
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
}).catch(error => {
|
||||
if(this.config_data.daemon.type == "remote") {
|
||||
this.send("show_notification", {type: "negative", message: "Remote daemon cannot be reached", timeout: 2000})
|
||||
} else {
|
||||
this.send("show_notification", {type: "negative", message: "Local daemon internal error", timeout: 2000})
|
||||
})
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// If we got a net type back then check if ours match
|
||||
if (data.net_type && data.net_type !== net_type) {
|
||||
this.send("show_notification", {
|
||||
type: "negative",
|
||||
message: "Error: Remote node is using a different nettype",
|
||||
timeout: 2000
|
||||
})
|
||||
|
||||
// Go back to config
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
this.daemon.checkVersion().then((version) => {
|
||||
if (version) {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 4,
|
||||
message: version
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// daemon not found, probably removed by AV, set to remote node
|
||||
this.config_data.daemons[net_type].type = "remote"
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 5
|
||||
},
|
||||
config: this.config_data,
|
||||
pending_config: this.config_data
|
||||
})
|
||||
}
|
||||
});
|
||||
return;
|
||||
});
|
||||
|
||||
});
|
||||
this.daemon.start(this.config_data).then(() => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 6 // Starting wallet
|
||||
}
|
||||
})
|
||||
|
||||
this.walletd.start(this.config_data).then(() => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 7 // Reading wallet list
|
||||
}
|
||||
})
|
||||
|
||||
this.walletd.listWallets(true)
|
||||
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: 0 // Ready
|
||||
}
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
if (this.config_data.daemons[net_type].type == "remote") {
|
||||
this.send("show_notification", {type: "negative", message: "Remote daemon cannot be reached", timeout: 2000})
|
||||
} else {
|
||||
this.send("show_notification", {type: "negative", message: "Local daemon internal error", timeout: 2000})
|
||||
}
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
// eslint-disable-next-line
|
||||
}).catch(error => {
|
||||
this.send("set_app_data", {
|
||||
status: {
|
||||
code: -1 // Return to config screen
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
quit() {
|
||||
quit () {
|
||||
return new Promise((resolve, reject) => {
|
||||
let process = []
|
||||
if(this.daemon)
|
||||
process.push(this.daemon.quit())
|
||||
if(this.walletd)
|
||||
process.push(this.walletd.quit())
|
||||
if(this.wss)
|
||||
this.wss.close();
|
||||
if (this.daemon) { process.push(this.daemon.quit()) }
|
||||
if (this.walletd) { process.push(this.walletd.quit()) }
|
||||
if (this.wss) { this.wss.close() }
|
||||
|
||||
Promise.all(process).then(() => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Replace any invalid value with default values
|
||||
validate_values (values, defaults) {
|
||||
const isDictionary = (v) => typeof v === "object" && v !== null && !(v instanceof Array) && !(v instanceof Date)
|
||||
const modified = { ...values }
|
||||
|
||||
// Make sure we have valid defaults
|
||||
if (!isDictionary(defaults)) return modified
|
||||
|
||||
for (const key in modified) {
|
||||
// Only modify if we have a default
|
||||
if (!(key in defaults)) continue
|
||||
|
||||
const defaultValue = defaults[key]
|
||||
const invalidDefault = defaultValue === null || defaultValue === undefined || Number.isNaN(defaultValue)
|
||||
if (invalidDefault) continue
|
||||
|
||||
const value = modified[key]
|
||||
|
||||
// If we have a object then recurse through it
|
||||
if (isDictionary(value)) {
|
||||
modified[key] = this.validate_values(value, defaultValue)
|
||||
} else {
|
||||
// Check if we need to replace the value
|
||||
const isValidValue = !(value === undefined || value === null || value === "" || Number.isNaN(value))
|
||||
if (isValidValue) continue
|
||||
|
||||
// Otherwise set the default value
|
||||
modified[key] = defaultValue
|
||||
}
|
||||
}
|
||||
return modified
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,65 +1,76 @@
|
|||
import child_process from "child_process";
|
||||
const request = require("request-promise");
|
||||
const queue = require("promise-queue");
|
||||
const http = require("http");
|
||||
const fs = require("fs");
|
||||
const path = require("path");
|
||||
import child_process from "child_process"
|
||||
const request = require("request-promise")
|
||||
const queue = require("promise-queue")
|
||||
const http = require("http")
|
||||
const fs = require("fs")
|
||||
const path = require("path")
|
||||
|
||||
export class Daemon {
|
||||
constructor(backend) {
|
||||
constructor (backend) {
|
||||
this.backend = backend
|
||||
this.heartbeat = null
|
||||
this.heartbeat_slow = null
|
||||
this.id = 0
|
||||
this.testnet = false
|
||||
this.net_type = "mainnet"
|
||||
this.local = false // do we have a local daemon ?
|
||||
|
||||
this.agent = new http.Agent({keepAlive: true, maxSockets: 1})
|
||||
this.queue = new queue(1, Infinity)
|
||||
|
||||
}
|
||||
|
||||
|
||||
checkVersion() {
|
||||
checkVersion () {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (process.platform === "win32") {
|
||||
let ryod_path = path.join(__ryo_bin, "ryod.exe")
|
||||
let ryod_version_cmd = `"${ryod_path}" --version`
|
||||
if (!fs.existsSync(ryod_path))
|
||||
resolve(false)
|
||||
child_process.exec(ryod_version_cmd, (error, stdout, stderr) => {
|
||||
if(error)
|
||||
resolve(false)
|
||||
let lokid_path = path.join(__ryo_bin, "lokid.exe")
|
||||
let lokid_version_cmd = `"${lokid_path}" --version`
|
||||
if (!fs.existsSync(lokid_path)) { resolve(false) }
|
||||
child_process.exec(lokid_version_cmd, (error, stdout, stderr) => {
|
||||
if (error) { resolve(false) }
|
||||
resolve(stdout)
|
||||
})
|
||||
} else {
|
||||
let ryod_path = path.join(__ryo_bin, "ryod")
|
||||
let ryod_version_cmd = `"${ryod_path}" --version`
|
||||
if (!fs.existsSync(ryod_path))
|
||||
resolve(false)
|
||||
child_process.exec(ryod_version_cmd, {detached: true}, (error, stdout, stderr) => {
|
||||
if(error)
|
||||
resolve(false)
|
||||
let lokid_path = path.join(__ryo_bin, "lokid")
|
||||
let lokid_version_cmd = `"${lokid_path}" --version`
|
||||
if (!fs.existsSync(lokid_path)) { resolve(false) }
|
||||
child_process.exec(lokid_version_cmd, {detached: true}, (error, stdout, stderr) => {
|
||||
if (error) { resolve(false) }
|
||||
resolve(stdout)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start(options) {
|
||||
checkRemote (daemon) {
|
||||
if (daemon.type === "local") {
|
||||
return Promise.resolve({})
|
||||
}
|
||||
|
||||
if(options.daemon.type === "remote") {
|
||||
return this.sendRPC("get_info", {}, {
|
||||
protocol: "http://",
|
||||
hostname: daemon.remote_host,
|
||||
port: daemon.remote_port
|
||||
}).then(data => {
|
||||
if (data.error) return { error: data.error }
|
||||
return {
|
||||
net_type: data.result.nettype
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
start (options) {
|
||||
const { net_type } = options.app
|
||||
const daemon = options.daemons[net_type]
|
||||
if (daemon.type === "remote") {
|
||||
this.local = false
|
||||
|
||||
// save this info for later RPC calls
|
||||
this.protocol = "http://"
|
||||
this.hostname = options.daemon.remote_host
|
||||
this.port = options.daemon.remote_port
|
||||
this.hostname = daemon.remote_host
|
||||
this.port = daemon.remote_port
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.sendRPC("get_info").then((data) => {
|
||||
if(!data.hasOwnProperty("error")) {
|
||||
if (!data.hasOwnProperty("error")) {
|
||||
this.startHeartbeat()
|
||||
resolve()
|
||||
} else {
|
||||
|
@ -69,54 +80,62 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
this.local = true
|
||||
|
||||
const args = [
|
||||
"--data-dir", options.app.data_dir,
|
||||
"--p2p-bind-ip", options.daemon.p2p_bind_ip,
|
||||
"--p2p-bind-port", options.daemon.p2p_bind_port,
|
||||
"--rpc-bind-ip", options.daemon.rpc_bind_ip,
|
||||
"--rpc-bind-port", options.daemon.rpc_bind_port,
|
||||
"--zmq-rpc-bind-ip", options.daemon.zmq_rpc_bind_ip,
|
||||
"--zmq-rpc-bind-port", options.daemon.zmq_rpc_bind_port,
|
||||
"--out-peers", options.daemon.out_peers,
|
||||
"--in-peers", options.daemon.in_peers,
|
||||
"--limit-rate-up", options.daemon.limit_rate_up,
|
||||
"--limit-rate-down", options.daemon.limit_rate_down,
|
||||
"--log-level", options.daemon.log_level,
|
||||
];
|
||||
"--p2p-bind-ip", daemon.p2p_bind_ip,
|
||||
"--p2p-bind-port", daemon.p2p_bind_port,
|
||||
"--rpc-bind-ip", daemon.rpc_bind_ip,
|
||||
"--rpc-bind-port", daemon.rpc_bind_port,
|
||||
"--zmq-rpc-bind-ip", daemon.zmq_rpc_bind_ip,
|
||||
"--zmq-rpc-bind-port", daemon.zmq_rpc_bind_port,
|
||||
"--out-peers", daemon.out_peers,
|
||||
"--in-peers", daemon.in_peers,
|
||||
"--limit-rate-up", daemon.limit_rate_up,
|
||||
"--limit-rate-down", daemon.limit_rate_down,
|
||||
"--log-level", daemon.log_level
|
||||
]
|
||||
|
||||
if(options.app.testnet) {
|
||||
this.testnet = true
|
||||
args.push("--testnet")
|
||||
args.push("--log-file", path.join(options.app.data_dir, "testnet", "logs", "ryod.log"))
|
||||
} else {
|
||||
args.push("--log-file", path.join(options.app.data_dir, "logs", "ryod.log"))
|
||||
const dirs = {
|
||||
"mainnet": options.app.data_dir,
|
||||
"stagenet": path.join(options.app.data_dir, "stagenet"),
|
||||
"testnet": path.join(options.app.data_dir, "testnet")
|
||||
}
|
||||
|
||||
if(options.daemon.rpc_bind_ip !== "127.0.0.1")
|
||||
args.push("--confirm-external-bind")
|
||||
const { net_type } = options.app
|
||||
this.net_type = net_type
|
||||
|
||||
if(options.daemon.type === "local_remote" && !options.app.testnet) {
|
||||
if (net_type === "testnet") {
|
||||
args.push("--testnet")
|
||||
} else if (net_type === "stagenet") {
|
||||
args.push("--stagenet")
|
||||
}
|
||||
|
||||
args.push("--log-file", path.join(dirs[net_type], "logs", "lokid.log"))
|
||||
|
||||
if (daemon.rpc_bind_ip !== "127.0.0.1") { args.push("--confirm-external-bind") }
|
||||
|
||||
// TODO: Check if we need to push this command for staging too
|
||||
if (daemon.type === "local_remote" && net_type === "mainnet") {
|
||||
args.push(
|
||||
"--bootstrap-daemon-address",
|
||||
`${options.daemon.remote_host}:${options.daemon.remote_port}`
|
||||
`${daemon.remote_host}:${daemon.remote_port}`
|
||||
)
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "ryod.exe"), args)
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "lokid.exe"), args)
|
||||
} else {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "ryod"), args, {
|
||||
this.daemonProcess = child_process.spawn(path.join(__ryo_bin, "lokid"), args, {
|
||||
detached: true
|
||||
})
|
||||
}
|
||||
|
||||
// save this info for later RPC calls
|
||||
this.protocol = "http://"
|
||||
this.hostname = options.daemon.rpc_bind_ip
|
||||
this.port = options.daemon.rpc_bind_port
|
||||
this.hostname = daemon.rpc_bind_ip
|
||||
this.port = daemon.rpc_bind_port
|
||||
|
||||
this.daemonProcess.stdout.on("data", data => process.stdout.write(`Daemon: ${data}`))
|
||||
this.daemonProcess.on("error", err => process.stderr.write(`Daemon: ${err}`))
|
||||
|
@ -125,17 +144,17 @@ export class Daemon {
|
|||
// To let caller know when the daemon is ready
|
||||
let intrvl = setInterval(() => {
|
||||
this.sendRPC("get_info").then((data) => {
|
||||
if(!data.hasOwnProperty("error")) {
|
||||
if (!data.hasOwnProperty("error")) {
|
||||
this.startHeartbeat()
|
||||
clearInterval(intrvl);
|
||||
resolve();
|
||||
clearInterval(intrvl)
|
||||
resolve()
|
||||
} else {
|
||||
if(data.error.cause &&
|
||||
if (data.error.cause &&
|
||||
data.error.cause.code === "ECONNREFUSED") {
|
||||
// Ignore
|
||||
} else {
|
||||
clearInterval(intrvl);
|
||||
reject(error);
|
||||
clearInterval(intrvl)
|
||||
reject(error)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -143,26 +162,20 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
handle(data) {
|
||||
|
||||
handle (data) {
|
||||
let params = data.data
|
||||
|
||||
switch (data.method) {
|
||||
case "ban_peer":
|
||||
this.banPeer(params.host, params.seconds)
|
||||
break
|
||||
|
||||
case "ban_peer":
|
||||
this.banPeer(params.host, params.seconds)
|
||||
break
|
||||
|
||||
default:
|
||||
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
banPeer(host, seconds=3600) {
|
||||
|
||||
if(!seconds)
|
||||
seconds=3600
|
||||
banPeer (host, seconds = 3600) {
|
||||
if (!seconds) { seconds = 3600 }
|
||||
|
||||
let params = {
|
||||
bans: [{
|
||||
|
@ -173,51 +186,45 @@ export class Daemon {
|
|||
}
|
||||
|
||||
this.sendRPC("set_bans", params).then((data) => {
|
||||
if(data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
this.sendGateway("show_notification", {type: "negative", message: "Error banning peer", timeout: 2000})
|
||||
return
|
||||
}
|
||||
|
||||
let end_time = new Date(Date.now() + seconds * 1000).toLocaleString()
|
||||
this.sendGateway("show_notification", {message: "Banned "+host+" until "+end_time, timeout: 2000})
|
||||
this.sendGateway("show_notification", {message: "Banned " + host + " until " + end_time, timeout: 2000})
|
||||
|
||||
// Send updated peer and ban list
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
timestampToHeight(timestamp, pivot=null, recursion_limit=null) {
|
||||
|
||||
timestampToHeight (timestamp, pivot = null, recursion_limit = null) {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(timestamp > 999999999999) {
|
||||
if (timestamp > 999999999999) {
|
||||
// We have got a JS ms timestamp, convert
|
||||
timestamp = Math.floor(timestamp / 1000)
|
||||
}
|
||||
|
||||
pivot = pivot || [137500, 1528073506]
|
||||
recursion_limit = recursion_limit || 0;
|
||||
recursion_limit = recursion_limit || 0
|
||||
|
||||
let diff = Math.floor((timestamp - pivot[1]) / 240)
|
||||
let estimated_height = pivot[0] + diff
|
||||
|
||||
if(estimated_height <= 0) {
|
||||
if (estimated_height <= 0) {
|
||||
return resolve(0)
|
||||
}
|
||||
|
||||
if(recursion_limit > 10) {
|
||||
if (recursion_limit > 10) {
|
||||
return resolve(pivot[0])
|
||||
}
|
||||
|
||||
this.getRPC("block_header_by_height", {height: estimated_height}).then((data) => {
|
||||
|
||||
if(data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if(data.error.code == -2) { // Too big height
|
||||
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if (data.error.code == -2) { // Too big height
|
||||
this.getRPC("last_block_header").then((data) => {
|
||||
if(data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
if (data.hasOwnProperty("error") || !data.hasOwnProperty("result")) {
|
||||
return reject()
|
||||
}
|
||||
|
||||
|
@ -226,7 +233,7 @@ export class Daemon {
|
|||
// If we are within an hour that is good enough
|
||||
// If for some reason there is a > 1h gap between blocks
|
||||
// the recursion limit will take care of infinite loop
|
||||
if(Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
if (Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
return resolve(new_pivot[0])
|
||||
}
|
||||
|
||||
|
@ -244,26 +251,23 @@ export class Daemon {
|
|||
// If we are within an hour that is good enough
|
||||
// If for some reason there is a > 1h gap between blocks
|
||||
// the recursion limit will take care of infinite loop
|
||||
if(Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
if (Math.abs(timestamp - new_pivot[1]) < 3600) {
|
||||
return resolve(new_pivot[0])
|
||||
}
|
||||
|
||||
// Continue recursion with new pivot
|
||||
resolve(new_pivot)
|
||||
|
||||
})
|
||||
}).then((pivot_or_height) => {
|
||||
|
||||
return Array.isArray(pivot_or_height)
|
||||
? this.timestampToHeight(timestamp, pivot_or_height, recursion_limit + 1)
|
||||
: pivot_or_height
|
||||
|
||||
}).catch(error => {
|
||||
return false
|
||||
})
|
||||
}
|
||||
|
||||
startHeartbeat() {
|
||||
startHeartbeat () {
|
||||
clearInterval(this.heartbeat)
|
||||
this.heartbeat = setInterval(() => {
|
||||
this.heartbeatAction()
|
||||
|
@ -275,14 +279,13 @@ export class Daemon {
|
|||
this.heartbeatSlowAction()
|
||||
}, 30 * 1000) // 30 seconds
|
||||
this.heartbeatSlowAction()
|
||||
|
||||
}
|
||||
|
||||
heartbeatAction() {
|
||||
heartbeatAction () {
|
||||
let actions = []
|
||||
|
||||
// No difference between local and remote heartbeat action for now
|
||||
if(this.local) {
|
||||
if (this.local) {
|
||||
actions = [
|
||||
this.getRPC("info")
|
||||
]
|
||||
|
@ -296,9 +299,8 @@ export class Daemon {
|
|||
let daemon_info = {
|
||||
}
|
||||
for (let n of data) {
|
||||
if(n == undefined || !n.hasOwnProperty("result") || n.result == undefined)
|
||||
continue
|
||||
if(n.method == "get_info") {
|
||||
if (n == undefined || !n.hasOwnProperty("result") || n.result == undefined) { continue }
|
||||
if (n.method == "get_info") {
|
||||
daemon_info.info = n.result
|
||||
}
|
||||
}
|
||||
|
@ -306,28 +308,27 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
heartbeatSlowAction() {
|
||||
heartbeatSlowAction () {
|
||||
let actions = []
|
||||
if(this.local) {
|
||||
if (this.local) {
|
||||
actions = [
|
||||
this.getRPC("connections"),
|
||||
this.getRPC("bans"),
|
||||
//this.getRPC("txpool_backlog"),
|
||||
this.getRPC("bans")
|
||||
// this.getRPC("txpool_backlog"),
|
||||
]
|
||||
} else {
|
||||
actions = [
|
||||
//this.getRPC("txpool_backlog"),
|
||||
// this.getRPC("txpool_backlog"),
|
||||
]
|
||||
}
|
||||
|
||||
if(actions.length === 0) return
|
||||
if (actions.length === 0) return
|
||||
|
||||
Promise.all(actions).then((data) => {
|
||||
let daemon_info = {
|
||||
}
|
||||
for (let n of data) {
|
||||
if(n == undefined || !n.hasOwnProperty("result") || n.result == undefined)
|
||||
continue
|
||||
if (n == undefined || !n.hasOwnProperty("result") || n.result == undefined) { continue }
|
||||
if (n.method == "get_connections" && n.result.hasOwnProperty("connections")) {
|
||||
daemon_info.connections = n.result.connections
|
||||
} else if (n.method == "get_bans" && n.result.hasOwnProperty("bans")) {
|
||||
|
@ -340,14 +341,19 @@ export class Daemon {
|
|||
})
|
||||
}
|
||||
|
||||
sendGateway(method, data) {
|
||||
sendGateway (method, data) {
|
||||
this.backend.send(method, data)
|
||||
}
|
||||
|
||||
sendRPC(method, params={}) {
|
||||
sendRPC (method, params = {}, options = {}) {
|
||||
let id = this.id++
|
||||
let options = {
|
||||
uri: `${this.protocol}${this.hostname}:${this.port}/json_rpc`,
|
||||
|
||||
const protocol = options.protocol || this.protocol
|
||||
const hostname = options.hostname || this.hostname
|
||||
const port = options.port || this.port
|
||||
|
||||
let requestOptions = {
|
||||
uri: `${protocol}${hostname}:${port}/json_rpc`,
|
||||
method: "POST",
|
||||
json: {
|
||||
jsonrpc: "2.0",
|
||||
|
@ -355,15 +361,15 @@ export class Daemon {
|
|||
method: method
|
||||
},
|
||||
agent: this.agent
|
||||
};
|
||||
if(Object.keys(params).length !== 0) {
|
||||
options.json.params = params;
|
||||
}
|
||||
if (Object.keys(params).length !== 0) {
|
||||
requestOptions.json.params = params
|
||||
}
|
||||
|
||||
return this.queue.add(() => {
|
||||
return request(options)
|
||||
return request(requestOptions)
|
||||
.then((response) => {
|
||||
if(response.hasOwnProperty("error")) {
|
||||
if (response.hasOwnProperty("error")) {
|
||||
return {
|
||||
method: method,
|
||||
params: params,
|
||||
|
@ -392,12 +398,12 @@ export class Daemon {
|
|||
/**
|
||||
* Call one of the get_* RPC calls
|
||||
*/
|
||||
getRPC(parameter, args) {
|
||||
return this.sendRPC(`get_${parameter}`, args);
|
||||
getRPC (parameter, args) {
|
||||
return this.sendRPC(`get_${parameter}`, args)
|
||||
}
|
||||
|
||||
quit() {
|
||||
clearInterval(this.heartbeat);
|
||||
quit () {
|
||||
clearInterval(this.heartbeat)
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.daemonProcess) {
|
||||
this.daemonProcess.on("close", code => {
|
||||
|
|
|
@ -15,72 +15,52 @@
|
|||
<q-btn class="q-ml-sm" color="primary" @click="save()" label="Save" />
|
||||
|
||||
</q-toolbar>
|
||||
<div>
|
||||
<div class="address-book-modal q-mx-md">
|
||||
<LokiField label="Address" :error="$v.newEntry.address.$error">
|
||||
<q-input
|
||||
v-model="newEntry.address"
|
||||
:placeholder="address_placeholder"
|
||||
@blur="$v.newEntry.address.$touch"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
<q-checkbox
|
||||
v-model="newEntry.starred"
|
||||
checked-icon="star"
|
||||
unchecked-icon="star_border"
|
||||
class="star-entry"
|
||||
dark
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField label="Name">
|
||||
<q-input
|
||||
v-model="newEntry.name"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField label="Payment ID" :error="$v.newEntry.payment_id.$error" optional>
|
||||
<q-input
|
||||
v-model="newEntry.payment_id"
|
||||
placeholder="16 or 64 hexadecimal characters"
|
||||
@blur="$v.newEntry.payment_id.$touch"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField label="Notes" optional>
|
||||
<q-input
|
||||
v-model="newEntry.description"
|
||||
placeholder="Additional notes"
|
||||
type="textarea"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-list no-border :dark="theme=='dark'">
|
||||
|
||||
<q-item>
|
||||
<q-item-side class="self-start">
|
||||
<Identicon :address="newEntry.address" menu />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-input v-model="newEntry.address" float-label="Address"
|
||||
@blur="$v.newEntry.address.$touch"
|
||||
:error="$v.newEntry.address.$error"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-input v-model="newEntry.name" float-label="Name" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
<q-item-side class="self-start q-pa-sm">
|
||||
<q-checkbox
|
||||
v-model="newEntry.starred"
|
||||
checked-icon="star"
|
||||
unchecked-icon="star_border"
|
||||
class="star-entry"
|
||||
/>
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-input v-model="newEntry.payment_id" float-label="Payment ID (optional)"
|
||||
@blur="$v.newEntry.payment_id.$touch"
|
||||
:error="$v.newEntry.payment_id.$error"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-input v-model="newEntry.description" type="textarea" float-label="Notes (optional)" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
|
||||
<q-item v-if="mode=='edit'">
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-btn class="float-right" color="red" @click="deleteEntry()" label="Delete" />
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
|
||||
</q-list>
|
||||
<q-field v-if="mode=='edit'">
|
||||
<q-btn class="float-right" color="red" @click="deleteEntry()" label="Delete" />
|
||||
</q-field>
|
||||
</div>
|
||||
</q-modal-layout>
|
||||
|
||||
|
@ -119,7 +99,7 @@
|
|||
<span class="vertical-middle q-ml-xs">Recent transactions with this address</span>
|
||||
</div>
|
||||
|
||||
<TxList type="in" :limit="5" :to-outgoing-address="entry.address" />
|
||||
<TxList type="all_in" :limit="5" :to-outgoing-address="entry.address" :key="entry.address"/>
|
||||
|
||||
</div>
|
||||
|
||||
|
@ -137,6 +117,7 @@ import { mapState } from "vuex"
|
|||
import Identicon from "components/identicon"
|
||||
import AddressHeader from "components/address_header"
|
||||
import TxList from "components/tx_list"
|
||||
import LokiField from "components/loki_field"
|
||||
import { payment_id, address } from "src/validators/common"
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
export default {
|
||||
|
@ -161,11 +142,27 @@ export default {
|
|||
view_only: state => state.gateway.wallet.info.view_only,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
address_placeholder (state) {
|
||||
const wallet = state.gateway.wallet.info;
|
||||
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
|
||||
return `${prefix}..`;
|
||||
}
|
||||
}),
|
||||
validations: {
|
||||
newEntry: {
|
||||
address: { required, address },
|
||||
address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
},
|
||||
payment_id: { payment_id }
|
||||
}
|
||||
},
|
||||
|
@ -235,7 +232,8 @@ export default {
|
|||
components: {
|
||||
AddressHeader,
|
||||
Identicon,
|
||||
TxList
|
||||
TxList,
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -243,12 +241,14 @@ export default {
|
|||
<style lang="scss">
|
||||
.address-book-details {
|
||||
|
||||
.q-field {
|
||||
margin: 0 10px 20px;
|
||||
}
|
||||
.q-checkbox.star-entry .q-checkbox-icon {
|
||||
font-size:40px;
|
||||
margin-left: 10px;
|
||||
.address-book-modal {
|
||||
> .loki-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
|
||||
.star-entry {
|
||||
padding: 4px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<q-modal v-model="isVisible" maximized :content-css="{padding: '50px'}">
|
||||
<q-modal v-model="isVisible" maximized>
|
||||
<q-modal-layout>
|
||||
<q-toolbar slot="header" color="dark" inverted>
|
||||
<q-btn
|
||||
|
@ -23,6 +23,7 @@
|
|||
<AddressHeader :address="address.address"
|
||||
:title="address.address_index == 0 ? 'Primary address' : 'Sub-address (Index '+address.address_index+')'"
|
||||
:extra="'You have '+(address.used?'used':'not used')+' this address'"
|
||||
:showCopy="false"
|
||||
/>
|
||||
|
||||
|
||||
|
@ -32,14 +33,14 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="address.balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="address.balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Unlocked balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="address.unlocked_balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="address.unlocked_balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -87,7 +88,7 @@
|
|||
</div>
|
||||
|
||||
<div style="margin: 0 -16px;">
|
||||
<TxList type="in" :limit="5" :to-incoming-address-index="address.address_index" />
|
||||
<TxList type="all_in" :limit="5" :to-incoming-address-index="address.address_index" :key="address.address"/>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
@ -106,6 +107,9 @@
|
|||
</qrcode-vue>
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay @click.native="copyQR()">
|
||||
<q-item-main label="Copy QR code" />
|
||||
</q-item>
|
||||
<q-item v-close-overlay @click.native="saveQR()">
|
||||
<q-item-main label="Save QR code to file" />
|
||||
</q-item>
|
||||
|
@ -126,9 +130,9 @@
|
|||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
const {clipboard} = require("electron")
|
||||
const { clipboard, nativeImage } = require("electron")
|
||||
import AddressHeader from "components/address_header"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import TxList from "components/tx_list"
|
||||
export default {
|
||||
|
@ -143,6 +147,16 @@ export default {
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
copyQR () {
|
||||
const data = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
const img = nativeImage.createFromDataURL(data)
|
||||
clipboard.writeImage(img)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Copied QR code to clipboard"
|
||||
})
|
||||
},
|
||||
saveQR() {
|
||||
let img = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
this.$gateway.send("core", "save_png", {img, type: "QR Code"})
|
||||
|
@ -159,7 +173,7 @@ export default {
|
|||
components: {
|
||||
AddressHeader,
|
||||
TxList,
|
||||
FormatRyo,
|
||||
FormatLoki,
|
||||
QrcodeVue
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,17 +1,15 @@
|
|||
<template>
|
||||
<q-item class="address-header">
|
||||
<q-item-side>
|
||||
<Identicon :address="address" :size="12" ref="identicon" />
|
||||
</q-item-side>
|
||||
<q-item-main class="self-start">
|
||||
<q-item-tile label>{{ title }}</q-item-tile>
|
||||
<q-item-tile class="monospace break-all" sublabel>{{ address }}</q-item-tile>
|
||||
<q-item-tile sublabel class="title">{{ title }}</q-item-tile>
|
||||
<q-item-tile class="break-all" label>{{ address }}</q-item-tile>
|
||||
<q-item-tile v-if="payment_id" sublabel>Payment id: {{ payment_id }}</q-item-tile>
|
||||
<q-item-tile v-if="extra" sublabel>{{ extra }}</q-item-tile>
|
||||
<q-item-tile v-if="extra" sublabel class="extra">{{ extra }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-item-side v-if="showCopy">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
color="primary"
|
||||
style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
ref="copy"
|
||||
@click="copyAddress">
|
||||
|
@ -28,11 +26,6 @@
|
|||
@click.native="copyAddress(address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs.identicon.saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
|
@ -60,6 +53,11 @@ export default {
|
|||
extra: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
showCopy: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: true
|
||||
}
|
||||
},
|
||||
data () {
|
||||
|
@ -125,7 +123,20 @@ export default {
|
|||
|
||||
.q-item-main {
|
||||
.q-item-label {
|
||||
font-size:2em;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.q-item-sublabel, .q-list-header {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 14px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.extra {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,21 +1,24 @@
|
|||
<template>
|
||||
<q-layout-footer class="status-footer">
|
||||
<div class="status-line">
|
||||
<div class="status-line row items-center">
|
||||
<div class="status row items-center">
|
||||
<span>Status:</span>
|
||||
<span class="status-text" :class="[status]">{{ status | upperCase }}</span>
|
||||
</div>
|
||||
<div class="row">
|
||||
<template v-if="config_daemon.type !== 'remote'">
|
||||
<div>Daemon: {{ daemon.info.height_without_bootstrap }} / {{ target_height }} ({{ daemon_local_pct }}%)</div>
|
||||
</template>
|
||||
|
||||
<template v-if="config.daemon.type !== 'remote'">
|
||||
<div>Daemon: {{ daemon.info.height_without_bootstrap }} / {{ target_height }} ({{ daemon_local_pct }}%)</div>
|
||||
</template>
|
||||
<template v-if="config_daemon.type !== 'local'">
|
||||
<div>Remote: {{ daemon.info.height }}</div>
|
||||
</template>
|
||||
|
||||
<template v-if="config.daemon.type !== 'local'">
|
||||
<div>Remote: {{ daemon.info.height }}</div>
|
||||
</template>
|
||||
|
||||
<div>Wallet: {{ wallet.info.height }} / {{ target_height }} ({{ wallet_pct }}%)</div>
|
||||
|
||||
<div>{{ status }}</div>
|
||||
<div>Wallet: {{ wallet.info.height }} / {{ target_height }} ({{ wallet_pct }}%)</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="status-bars">
|
||||
<div class="status-bars" :class="[status]">
|
||||
<div v-bind:style="{ width: daemon_pct+'%' }"></div>
|
||||
<div v-bind:style="{ width: wallet_pct+'%' }"></div>
|
||||
</div>
|
||||
|
@ -32,19 +35,22 @@ export default {
|
|||
daemon: state => state.gateway.daemon,
|
||||
wallet: state => state.gateway.wallet,
|
||||
|
||||
config_daemon (state) {
|
||||
return this.config.daemons[this.config.app.net_type]
|
||||
},
|
||||
target_height (state) {
|
||||
if(this.config.daemon.type === "local" && !this.daemon.info.is_ready)
|
||||
if(this.config_daemon.type === "local")
|
||||
return Math.max(this.daemon.info.height, this.daemon.info.target_height)
|
||||
else
|
||||
return this.daemon.info.height
|
||||
},
|
||||
daemon_pct (state) {
|
||||
if(this.config.daemon.type === "local")
|
||||
if(this.config_daemon.type === "local")
|
||||
return this.daemon_local_pct
|
||||
return 0
|
||||
},
|
||||
daemon_local_pct (state) {
|
||||
if(this.config.daemon.type === "remote")
|
||||
if(this.config_daemon.type === "remote")
|
||||
return 0
|
||||
let pct = (100 * this.daemon.info.height_without_bootstrap / this.target_height).toFixed(1)
|
||||
if(pct == 100.0 && this.daemon.info.height_without_bootstrap < this.target_height)
|
||||
|
@ -60,27 +66,32 @@ export default {
|
|||
return pct
|
||||
},
|
||||
status(state) {
|
||||
if(this.config.daemon.type === "local") {
|
||||
if(this.daemon.info.height_without_bootstrap < this.target_height || !this.daemon.info.is_ready) {
|
||||
return "Syncing..."
|
||||
if(this.config_daemon.type === "local") {
|
||||
if(this.daemon.info.height_without_bootstrap < this.target_height) {
|
||||
return "syncing"
|
||||
} else if(this.wallet.info.height < this.target_height - 1 && this.wallet.info.height != 0) {
|
||||
return "Scanning..."
|
||||
return "scanning"
|
||||
} else {
|
||||
return "Ready"
|
||||
return "ready"
|
||||
}
|
||||
} else {
|
||||
if(this.wallet.info.height < this.target_height - 1 && this.wallet.info.height != 0) {
|
||||
return "Scanning..."
|
||||
} else if(this.daemon.info.height_without_bootstrap < this.target_height) {
|
||||
return "Syncing..."
|
||||
return "scanning"
|
||||
} else if(this.config_daemon.type === "local_remote" && this.daemon.info.height_without_bootstrap < this.target_height) {
|
||||
return "syncing"
|
||||
} else {
|
||||
return "Ready"
|
||||
return "ready"
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
}),
|
||||
filters: {
|
||||
upperCase: function (status) {
|
||||
return status.toUpperCase();
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
<template>
|
||||
<span>
|
||||
{{ value }} Ryo
|
||||
{{ value }} LOKI
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "FormatRyo",
|
||||
name: "FormatLoki",
|
||||
props: {
|
||||
amount: {
|
||||
required: true
|
95
src/components/loki_field.vue
Normal file
|
@ -0,0 +1,95 @@
|
|||
<template>
|
||||
<div class="loki-field" :class="{disable, 'disable-hover': disableHover}">
|
||||
<div class="label row items-center" v-if="label" :disabled="disable">
|
||||
{{ label }}
|
||||
<span v-if="optional" class="optional">(Optional)</span>
|
||||
</div>
|
||||
<div class="content row items-center" :class="{error}">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="error-label" v-if="error && errorLabel" :disabled="disable">{{ errorLabel }}</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: "LokiField",
|
||||
props: {
|
||||
label: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
error: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
errorLabel: {
|
||||
type: String,
|
||||
required: false
|
||||
},
|
||||
optional: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disable: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
},
|
||||
disableHover: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.loki-field {
|
||||
.label {
|
||||
margin: 6px 0;
|
||||
font-weight: bold;
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
|
||||
// Disable text selection
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
cursor: default;
|
||||
|
||||
.optional {
|
||||
font-weight: 400;
|
||||
margin-left: 4px;
|
||||
}
|
||||
}
|
||||
.content {
|
||||
border-radius: 3px;
|
||||
padding: 6px 8px;
|
||||
min-height: 46px;
|
||||
|
||||
> * {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.q-input, .q-select, .q-datetime-input {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.q-btn {
|
||||
padding: 4px 8px;
|
||||
font-size: 12px !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<div>
|
||||
<q-btn class="menu" icon="menu" label="" size="md" flat>
|
||||
<q-btn class="menu" icon="menu" size="md" flat>
|
||||
<q-popover>
|
||||
<q-list separator link>
|
||||
<q-item v-close-overlay @click.native="switchWallet" v-if="!disableSwitchWallet">
|
||||
|
@ -20,7 +20,7 @@
|
|||
</q-item>
|
||||
<q-item v-close-overlay @click.native="exit">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Exit Ryo GUI Wallet</q-item-tile>
|
||||
<q-item-tile label>Exit Loki GUI Wallet</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
@ -31,20 +31,22 @@
|
|||
<q-modal minimized ref="aboutModal">
|
||||
<div class="about-modal">
|
||||
|
||||
<img class="q-mb-md" src="statics/ryo-wallet.svg" height="42" />
|
||||
<img class="q-mb-md" src="statics/loki.svg" height="42" />
|
||||
|
||||
<p class="q-my-sm">Version: ATOM v{{version}}-v{{daemonVersion}}</p>
|
||||
<p class="q-my-sm">Copyright (c) 2018-2019, Loki Project</p>
|
||||
<p class="q-my-sm">Copyright (c) 2018, Ryo Currency Project</p>
|
||||
<p class="q-my-sm">All rights reserved.</p>
|
||||
|
||||
<div class="q-mt-md q-mb-lg external-links">
|
||||
<p>
|
||||
<a @click="openExternal('https://ryo-currency.com/')" href="#">https://ryo-currency.com/</a>
|
||||
<a @click="openExternal('https://loki.network/')" href="#">https://loki.network/</a>
|
||||
</p>
|
||||
<p>
|
||||
<a @click="openExternal('https://t.me/ryocurrency')" href="#">Telegram</a> -
|
||||
<a @click="openExternal('https://discord.gg/GFQmFtx')" href="#">Discord</a> -
|
||||
<a @click="openExternal('https://www.reddit.com/r/ryocurrency/')" href="#">Reddit</a>
|
||||
<a @click="openExternal('https://t.me/joinchat/DeNvR0JJ4JPn6TVSQjCsZQ')" href="#">Telegram</a> -
|
||||
<a @click="openExternal('https://discordapp.com/invite/67GXfD6')" href="#">Discord</a> -
|
||||
<a @click="openExternal('https://www.reddit.com/r/LokiProject/')" href="#">Reddit</a> -
|
||||
<a @click="openExternal('https://github.com/loki-project/loki-electron-wallet')" href="#">Github</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
@ -102,7 +104,7 @@ export default {
|
|||
title: "Switch wallet",
|
||||
message: "Are you sure you want to close the current wallet?",
|
||||
ok: {
|
||||
label: "CLOSE"
|
||||
label: "OK"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
|
|
120
src/components/service_node_registration.vue
Normal file
|
@ -0,0 +1,120 @@
|
|||
<template>
|
||||
<div class="service-node-registration">
|
||||
<div class="q-pa-md">
|
||||
<div class="description q-mb-lg">
|
||||
Enter the <b>register_service_node</b> command produced by the daemon that is registering to become a Service Node using the "<b>prepare_registration</b>" command
|
||||
</div>
|
||||
<LokiField label="Service Node Command" :error="$v.registration_string.$error" :disabled="registration_status.sending">
|
||||
<q-input
|
||||
v-model="registration_string"
|
||||
type="textarea"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.registration_string.$touch"
|
||||
placeholder="register_service_node ..."
|
||||
:disabled="registration_status.sending"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn color="primary" @click="register()" label="Register service node" :disabled="registration_status.sending"/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :visible="registration_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeRegistration",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
registration_status: state => state.gateway.service_node_status.registration,
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
registration_string: "",
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
registration_string: { required }
|
||||
},
|
||||
watch: {
|
||||
registration_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.registration_status.code) {
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.registration_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.registration_string = ""
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 3000,
|
||||
message: this.registration_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
register: function() {
|
||||
this.$v.registration_string.$touch()
|
||||
|
||||
if (this.$v.registration_string.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Please enter the service node registration command"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Register service node",
|
||||
noPasswordMessage: "Do you want to register the service node?",
|
||||
ok: {
|
||||
label: "REGISTER"
|
||||
},
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
registration: {
|
||||
code: 1,
|
||||
message: "Registering...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
this.$gateway.send("wallet", "register_service_node", {
|
||||
password,
|
||||
string: this.registration_string
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
276
src/components/service_node_staking.vue
Normal file
|
@ -0,0 +1,276 @@
|
|||
<template>
|
||||
<div class="service-node-staking">
|
||||
<div class="q-pa-md">
|
||||
<LokiField label="Service Node Key" :error="$v.service_node.key.$error">
|
||||
<q-input v-model="service_node.key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.key.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<div class="q-mt-md col">
|
||||
<LokiField label="Award Recepient's Address" :error="$v.service_node.award_address.$error">
|
||||
<q-input v-model="service_node.award_address"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.service_node.award_address.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<div class="address-type" :class="[addressType]">( {{ addressType | addressTypeString }} )</div>
|
||||
</div>
|
||||
|
||||
<LokiField label="Amount" class="q-mt-md" :error="$v.service_node.amount.$error">
|
||||
<q-input v-model="service_node.amount"
|
||||
:dark="theme=='dark'"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="unlocked_balance / 1e9"
|
||||
placeholder="0"
|
||||
@blur="$v.service_node.amount.$touch"
|
||||
hide-underline
|
||||
/>
|
||||
<q-btn color="secondary" @click="service_node.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
|
||||
</LokiField>
|
||||
|
||||
|
||||
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn
|
||||
:disable="!is_able_to_send"
|
||||
color="primary" @click="stake()" label="Stake" />
|
||||
</q-field>
|
||||
|
||||
</div>
|
||||
|
||||
<q-inner-loading :visible="stake_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required, decimal } from "vuelidate/lib/validators"
|
||||
import { payment_id, service_node_key, greater_than_zero, address } from "src/validators/common"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeStaking",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
unlocked_balance: state => state.gateway.wallet.info.unlocked_balance,
|
||||
info: state => state.gateway.wallet.info,
|
||||
address_list: state => state.gateway.wallet.address_list,
|
||||
stake_status: state => state.gateway.service_node_status.stake,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
is_able_to_send (state) {
|
||||
return this.$store.getters["gateway/isAbleToSend"]
|
||||
},
|
||||
|
||||
addressType (state) {
|
||||
const address = this.service_node.award_address;
|
||||
const inArray = (array) => array.map(o => o.address).includes(address);
|
||||
|
||||
const { primary, used, unused } = this.address_list
|
||||
if (inArray(primary)) {
|
||||
return "primary"
|
||||
} else if (inArray(used)) {
|
||||
return "used"
|
||||
} else if (inArray(unused)) {
|
||||
return "unsued"
|
||||
} else {
|
||||
return "not-ours"
|
||||
}
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
service_node: {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
},
|
||||
}
|
||||
},
|
||||
filters: {
|
||||
addressTypeString: function (value) {
|
||||
switch (value) {
|
||||
case "primary":
|
||||
return "Your primary address"
|
||||
case "used":
|
||||
return "Your used address"
|
||||
case "ununsed":
|
||||
return "Your unused address"
|
||||
default:
|
||||
return "Not your address!"
|
||||
}
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
service_node: {
|
||||
key: { required, service_node_key },
|
||||
amount: {
|
||||
required,
|
||||
decimal,
|
||||
greater_than_zero,
|
||||
},
|
||||
award_address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
stake_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.stake_status.code) {
|
||||
case 0:
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.service_node = {
|
||||
key: "",
|
||||
amount: 0,
|
||||
award_address: "",
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.stake_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
created () {
|
||||
const { address } = this.info;
|
||||
if (!this.service_node.award_address || this.service_node.award_address === "") {
|
||||
this.service_node.award_address = address || ""
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
isOurAddress (address) {
|
||||
const { primary, used, unused } = this.address_list
|
||||
const addresses = [...primary, ...used, ...unused].map(o => o.address);
|
||||
console.log(addresses);
|
||||
return addresses.includes(address);
|
||||
},
|
||||
stake: function () {
|
||||
|
||||
this.$v.service_node.$touch()
|
||||
|
||||
if (this.$v.service_node.key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (this.$v.service_node.award_address.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Address not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if(this.service_node.amount < 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount cannot be negative"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount == 0) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount must be greater than zero"
|
||||
})
|
||||
return
|
||||
} else if(this.service_node.amount > this.unlocked_balance / 1e9) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Not enough unlocked balance"
|
||||
})
|
||||
return
|
||||
} else if (this.$v.service_node.amount.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Amount not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Stake",
|
||||
noPasswordMessage: "Do you want to stake?",
|
||||
ok: {
|
||||
label: "STAKE"
|
||||
},
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
stake: {
|
||||
code: 1,
|
||||
message: "Staking...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
const service_node = objectAssignDeep.noMutate(this.service_node, {password})
|
||||
this.$gateway.send("wallet", "stake", {
|
||||
...service_node,
|
||||
destination: service_node.award_address,
|
||||
})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.service-node-staking {
|
||||
.address-type {
|
||||
margin-top: 4px;
|
||||
font-size: 13px;
|
||||
font-weight: 400;
|
||||
text-align: right;
|
||||
&.not-ours {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
147
src/components/service_node_unlock.vue
Normal file
|
@ -0,0 +1,147 @@
|
|||
<template>
|
||||
<div class="service-node-unlock">
|
||||
<div class="q-pa-md">
|
||||
<LokiField label="Service Node Key" :error="$v.node_key.$error" :disabled="unlock_status.sending">
|
||||
<q-input
|
||||
v-model="node_key"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.node_key.$touch"
|
||||
placeholder="64 hexadecimal characters"
|
||||
:disabled="unlock_status.sending"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn color="primary" @click="unlock()" label="Unlock service node" :disabled="unlock_status.sending"/>
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<q-inner-loading :visible="unlock_status.sending" :dark="theme=='dark'">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
</q-inner-loading>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
import { mapState } from "vuex"
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import { service_node_key } from "src/validators/common"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "ServiceNodeUnlock",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
unlock_status: state => state.gateway.service_node_status.unlock,
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
node_key: "",
|
||||
}
|
||||
},
|
||||
validations: {
|
||||
node_key: { required, service_node_key }
|
||||
},
|
||||
watch: {
|
||||
unlock_status: {
|
||||
handler(val, old){
|
||||
if(val.code == old.code) return
|
||||
switch(this.unlock_status.code) {
|
||||
case 0:
|
||||
this.key = null
|
||||
this.password = null
|
||||
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: this.unlock_status.message
|
||||
})
|
||||
this.$v.$reset();
|
||||
this.node_key = ""
|
||||
break;
|
||||
case 1:
|
||||
// Tell the user to confirm
|
||||
this.$q.dialog({
|
||||
title: "Confirm",
|
||||
message: this.unlock_status.message,
|
||||
ok: {
|
||||
label: "UNLOCK"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(() => {
|
||||
this.gatewayUnlock(this.password, this.key, true);
|
||||
}).catch(() => {})
|
||||
break;
|
||||
case -1:
|
||||
this.key = null
|
||||
this.password = null
|
||||
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.unlock_status.message
|
||||
})
|
||||
break;
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
unlock: function () {
|
||||
this.$v.node_key.$touch()
|
||||
if (this.$v.node_key.$error) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Service node key not valid"
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// We store this as it could change between the 2 step process
|
||||
this.key = this.node_key
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: "Unlock service node",
|
||||
noPasswordMessage: "Do you want to unlock the service node?",
|
||||
ok: {
|
||||
label: "UNLOCK"
|
||||
},
|
||||
}).then(password => {
|
||||
this.password = password
|
||||
this.gatewayUnlock(this.password, this.key, false);
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
gatewayUnlock: function (password, key, confirmed = false) {
|
||||
this.$store.commit("gateway/set_snode_status", {
|
||||
unlock: {
|
||||
code: 2, // Code 1 is reserved for can_unlock
|
||||
message: "Unlocking...",
|
||||
sending: true
|
||||
}
|
||||
})
|
||||
this.$gateway.send("wallet", "unlock_stake", {
|
||||
password,
|
||||
service_node_key: key,
|
||||
confirmed
|
||||
})
|
||||
},
|
||||
},
|
||||
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -11,6 +11,7 @@
|
|||
<q-btn-toggle
|
||||
v-model="page"
|
||||
toggle-color="primary"
|
||||
color="tertiary"
|
||||
size="md"
|
||||
:options="tabs"
|
||||
/>
|
||||
|
@ -25,22 +26,6 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="page=='appearance'">
|
||||
<div class="q-pa-md">
|
||||
<h6 class="q-mb-md q-mt-none" style="font-weight: 300">Select Appearance:</h6>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="theme"
|
||||
toggle-color="primary"
|
||||
size="md"
|
||||
:options="[
|
||||
{label: 'Light theme', value: 'light', icon: 'brightness_5'},
|
||||
{label: 'Dark theme', value: 'dark', icon: 'brightness_2'},
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="page=='peers'">
|
||||
<q-list :dark="theme=='dark'" no-border>
|
||||
<q-list-header>Peer list</q-list-header>
|
||||
|
@ -79,15 +64,16 @@ import SettingsGeneral from "components/settings_general"
|
|||
export default {
|
||||
name: "SettingsModal",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
daemon: state => state.gateway.daemon,
|
||||
pending_config: state => state.gateway.app.pending_config,
|
||||
config: state => state.gateway.app.config,
|
||||
tabs: function(state) {
|
||||
const { app, daemons } = state.gateway.app.config;
|
||||
let tabs = [
|
||||
{label: 'General', value: 'general', icon: 'settings'},
|
||||
{label: 'Appearance', value: 'appearance', icon: 'visibility'},
|
||||
]
|
||||
if(state.gateway.app.config.daemon.type != 'remote') {
|
||||
if(daemons[app.net_type].type != 'remote') {
|
||||
tabs.push({label: 'Peers', value: 'peers', icon: 'cloud_queue'})
|
||||
}
|
||||
return tabs
|
||||
|
@ -96,22 +82,10 @@ export default {
|
|||
data () {
|
||||
return {
|
||||
page: "general",
|
||||
theme: null,
|
||||
isVisible: false
|
||||
}
|
||||
},
|
||||
mounted: function () {
|
||||
this.theme = this.config.appearance.theme
|
||||
},
|
||||
watch: {
|
||||
theme: function (theme, old) {
|
||||
if(old == null) return
|
||||
this.$gateway.send("core", "quick_save_config", {
|
||||
appearance: {
|
||||
theme: this.theme
|
||||
}
|
||||
})
|
||||
},
|
||||
isVisible: function () {
|
||||
if(this.isVisible == false) {
|
||||
this.$store.dispatch("gateway/resetPendingConfig")
|
||||
|
|
|
@ -1,113 +1,157 @@
|
|||
<template>
|
||||
<div class="settings-general">
|
||||
<div class="row justify-between q-mb-md">
|
||||
<div><q-radio v-model="config.daemon.type" val="local_remote" label="Local + Remote Daemon" /></div>
|
||||
<div><q-radio v-model="config.daemon.type" val="local" label="Local Daemon Only" /></div>
|
||||
<div><q-radio v-model="config.daemon.type" val="remote" label="Remote Daemon Only" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="remote" label="Remote Daemon Only" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="local_remote" label="Local + Remote Daemon" /></div>
|
||||
<div><q-radio v-model="config_daemon.type" val="local" label="Local Daemon Only" /></div>
|
||||
</div>
|
||||
|
||||
<p v-if="config.daemon.type == 'local_remote'">
|
||||
<p v-if="config_daemon.type == 'local_remote'">
|
||||
Get started quickly with this default option. Wallet will download the full blockchain, but use a remote node while syncing.
|
||||
</p>
|
||||
<p v-if="config.daemon.type == 'local'">
|
||||
<p v-if="config_daemon.type == 'local'">
|
||||
Full security, wallet will download the full blockchain. You will not be able to transact until sync is completed.
|
||||
</p>
|
||||
<p v-if="config.daemon.type == 'remote'">
|
||||
<p v-if="is_remote">
|
||||
Less security, wallet will connect to a remote node to make all transactions.
|
||||
</p>
|
||||
|
||||
<q-field v-if="config.daemon.type != 'remote'">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-input v-model="config.daemon.rpc_bind_ip" float-label="Local Daemon IP"
|
||||
:dark="theme=='dark'" disable />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-model="config.daemon.rpc_bind_port" float-label="Local Daemon Port (RPC)" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
</div>
|
||||
<template v-if="config_daemon.type != 'remote'">
|
||||
<div class="row pl-sm">
|
||||
<LokiField class="col-8" label="Local Daemon IP" disable>
|
||||
<q-input
|
||||
v-model="config_daemon.rpc_bind_ip"
|
||||
:placeholder="daemon_defaults.rpc_bind_ip"
|
||||
:dark="theme=='dark'"
|
||||
disable
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField class="col-4" label="Local Daemon Port (RPC)">
|
||||
<q-input
|
||||
v-model="config_daemon.rpc_bind_port"
|
||||
:placeholder="toString(daemon_defaults.rpc_bind_port)"
|
||||
type="number"
|
||||
:decimals="0"
|
||||
:step="1"
|
||||
min="1024"
|
||||
max="65535"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
</q-field>
|
||||
</template>
|
||||
|
||||
<q-field v-if="config.daemon.type != 'local'">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-input v-model="config.daemon.remote_host" float-label="Remote Node Host" :dark="theme=='dark'" />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-input v-model="config.daemon.remote_port" float-label="Remote Node Port" type="number" :decimals="0" :step="1" min="1024" max="65535" :dark="theme=='dark'" />
|
||||
</div>
|
||||
<template v-if="config_daemon.type != 'local'">
|
||||
<div class="row q-mt-md pl-sm">
|
||||
<LokiField class="col-8" label="Remote Node Host">
|
||||
<q-input
|
||||
v-model="config_daemon.remote_host"
|
||||
:placeholder="daemon_defaults.remote_host"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
<!-- Remote node presets -->
|
||||
<q-btn-dropdown class="remote-dropdown" v-if="config.app.net_type === 'mainnet'" flat>
|
||||
<q-list link dark no-border>
|
||||
<q-item v-for="option in remotes" :key="option.host" @click.native="setPreset(option)" v-close-overlay>
|
||||
<q-item-main>
|
||||
<q-item-tile label>{{ option.host }}:{{ option.port }}</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-btn-dropdown>
|
||||
</LokiField>
|
||||
<LokiField class="col-4" label="Remote Node Port">
|
||||
<q-input
|
||||
v-model="config_daemon.remote_port"
|
||||
:placeholder="toString(daemon_defaults.remote_port)"
|
||||
type="number"
|
||||
:decimals="0"
|
||||
:step="1"
|
||||
min="1024"
|
||||
max="65535"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
</q-field>
|
||||
</template>
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-8">
|
||||
<q-input v-model="config.app.data_dir" stack-label="Data Storage Path" disable :dark="theme=='dark'" />
|
||||
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setDataPath" ref="fileInput" hidden />
|
||||
</div>
|
||||
<div class="col-4">
|
||||
<q-btn v-on:click="selectPath" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
|
||||
</div>
|
||||
<div class="col q-mt-md pt-sm">
|
||||
<LokiField label="Data Storage Path" disable-hover>
|
||||
<q-input v-model="config.app.data_dir" disable :dark="theme=='dark'" hide-underline/>
|
||||
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setDataPath" ref="fileInput" hidden />
|
||||
<q-btn color="secondary" v-on:click="selectPath" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
|
||||
</LokiField>
|
||||
<LokiField label="Wallet Storage Path" disable-hover>
|
||||
<q-input v-model="config.app.wallet_data_dir" disable :dark="theme=='dark'" hide-underline/>
|
||||
<input type="file" webkitdirectory directory id="dataPath" v-on:change="setWalletDataPath" ref="fileInput" hidden />
|
||||
<q-btn color="secondary" v-on:click="selectPath" :text-color="theme=='dark'?'white':'dark'">Select Location</q-btn>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<q-collapsible label="Advanced Options" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
|
||||
|
||||
<div class="row pl-sm q-mt-sm">
|
||||
<LokiField class="col-6" label="Daemon Log Level" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.log_level" :placeholder="toString(daemon_defaults.log_level)" :disable="is_remote" :dark="theme=='dark'"
|
||||
type="number" :decimals="0" :step="1" min="0" max="4" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-6" label="Wallet Log Level">
|
||||
<q-input v-model="config.wallet.log_level" :placeholder="toString(defaults.wallet.log_level)" :dark="theme=='dark'"
|
||||
type="number" :decimals="0" :step="1" min="0" max="4" hide-underline />
|
||||
</LokiField>
|
||||
</div>
|
||||
</q-field>
|
||||
|
||||
<q-collapsible label="Advanced Options" header-class="non-selectable row reverse advanced-options-label">
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.log_level" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon Log Level" type="number" :decimals="0" :step="1" min="0" max="4" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.wallet.log_level" :dark="theme=='dark'"
|
||||
float-label="Wallet Log Level" type="number" :decimals="0" :step="1" min="0" max="4" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-checkbox v-model="config.app.testnet" label="Testnet" />
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.in_peers" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Max Incoming Peers" type="number" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.out_peers" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Max Outgoing Peers" type="number" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.limit_rate_up" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Limit Upload Rate" type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.limit_rate_down" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Limit Download Rate" type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" />
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.p2p_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon P2P Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.daemon.zmq_rpc_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Daemon ZMQ Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.app.ws_bind_port" :dark="theme=='dark'"
|
||||
float-label="Internal Wallet Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-input v-model="config.wallet.rpc_bind_port" :disable="config.daemon.type == 'remote'" :dark="theme=='dark'"
|
||||
float-label="Wallet RPC Port" type="number" :decimals="0" :step="1" min="1024" max="65535" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="row pl-sm q-mt-md">
|
||||
<LokiField class="col-3" label="Max Incoming Peers" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.in_peers" :placeholder="toString(daemon_defaults.in_peers)" :disable="is_remote" :dark="theme=='dark'"
|
||||
type="number" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Max Outgoing Peers" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.out_peers" :placeholder="toString(daemon_defaults.out_peers)" :disable="is_remote" :dark="theme=='dark'"
|
||||
type="number" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Limit Upload Rate" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.limit_rate_up" :placeholder="toString(daemon_defaults.limit_rate_up)" :disable="is_remote" :dark="theme=='dark'"
|
||||
type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Limit Download Rate" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.limit_rate_down" :placeholder="toString(daemon_defaults.limit_rate_down)" :disable="is_remote" :dark="theme=='dark'"
|
||||
type="number" suffix="Kb/s" :decimals="0" :step="1" min="-1" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
</div>
|
||||
<div class="row pl-sm q-mt-md">
|
||||
<LokiField class="col-3" label="Daemon P2P Port" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.p2p_bind_port" :placeholder="toString(daemon_defaults.p2p_bind_port)" :disable="is_remote" :dark="theme=='dark'"
|
||||
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Daemon ZMQ Port" :disable="is_remote">
|
||||
<q-input v-model="config_daemon.zmq_rpc_bind_port" :placeholder="toString(daemon_defaults.zmq_rpc_bind_port)" :disable="is_remote" :dark="theme=='dark'"
|
||||
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Internal Wallet Port">
|
||||
<q-input v-model="config.app.ws_bind_port" :placeholder="toString(defaults.app.ws_bind_port)" :dark="theme=='dark'"
|
||||
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
<LokiField class="col-3" label="Wallet RPC Port" :disable="is_remote">
|
||||
<q-input v-model="config.wallet.rpc_bind_port" :placeholder="toString(defaults.wallet.rpc_bind_port)" :disable="is_remote" :dark="theme=='dark'"
|
||||
float- type="number" :decimals="0" :step="1" min="1024" max="65535" hide-underline />
|
||||
</LokiField>
|
||||
</div>
|
||||
<q-field helper="Choose a network" label="Network" orientation="vertical">
|
||||
<q-option-group
|
||||
type="radio"
|
||||
v-model="config.app.net_type"
|
||||
:options="[
|
||||
{ label: 'Main Net', value: 'mainnet' },
|
||||
{ label: 'Stage Net', value: 'stagenet' },
|
||||
{ label: 'Test Net', value: 'testnet' }
|
||||
]"
|
||||
/>
|
||||
</q-field>
|
||||
|
||||
</q-collapsible>
|
||||
|
@ -116,19 +160,66 @@
|
|||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
name: "SettingsGeneral",
|
||||
props: {
|
||||
randomise_remote: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
remotes: state => state.gateway.app.remotes,
|
||||
config: state => state.gateway.app.pending_config,
|
||||
config_daemon (state) {
|
||||
return this.config.daemons[this.config.app.net_type]
|
||||
},
|
||||
is_remote (state) {
|
||||
return this.config_daemon.type === 'remote'
|
||||
},
|
||||
defaults: state => state.gateway.app.defaults,
|
||||
daemon_defaults (state) {
|
||||
return this.defaults.daemons[this.config.app.net_type]
|
||||
}
|
||||
}),
|
||||
mounted () {
|
||||
if(this.randomise_remote && this.remotes.length > 0 && this.config.app.net_type === "mainnet") {
|
||||
const index = Math.floor(Math.random() * Math.floor(this.remotes.length));
|
||||
this.setPreset(this.remotes[index]);
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
selectPath () {
|
||||
this.$refs.fileInput.click()
|
||||
},
|
||||
setDataPath (file) {
|
||||
this.config.app.data_dir = file.target.files[0].path
|
||||
},
|
||||
setWalletDataPath (file) {
|
||||
this.config.app.wallet_data_dir = file.target.files[0].path
|
||||
},
|
||||
setPreset (option) {
|
||||
if (!option) return;
|
||||
|
||||
const { host, port } = option;
|
||||
if (host) this.config_daemon.remote_host = host;
|
||||
if (port) this.config_daemon.remote_port = port;
|
||||
},
|
||||
toString (value) {
|
||||
if (!value && typeof value !== "number") return ""
|
||||
return String(value);
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
select: 0,
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LokiField,
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@ -150,5 +241,21 @@ export default {
|
|||
.q-collapsible-sub-item {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.row.pl-sm {
|
||||
> * + * {
|
||||
padding-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.col.pt-sm {
|
||||
> * + * {
|
||||
padding-top: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
.remote-dropdown {
|
||||
padding: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<q-modal v-model="isVisible" maximized :content-css="{padding: '50px'}">
|
||||
<q-modal v-model="isVisible" maximized>
|
||||
<q-modal-layout>
|
||||
<q-toolbar slot="header" color="dark" inverted>
|
||||
<q-btn
|
||||
|
@ -46,14 +46,14 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Amount</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="tx.amount" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="tx.amount" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Fee <template v-if="tx.type=='in'||tx.type=='pool'">(paid by sender)</template></span></div>
|
||||
<div class="value"><span><FormatRyo :amount="tx.fee" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="tx.fee" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
@ -85,9 +85,6 @@
|
|||
<q-list no-border>
|
||||
<q-list-header class="q-px-none">Incoming transaction sent to:</q-list-header>
|
||||
<q-item class="q-px-none">
|
||||
<q-item-side>
|
||||
<Identicon :address="in_tx_address_used.address" ref="identicon" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile label>{{ in_tx_address_used.address_index_text }}</q-item-tile>
|
||||
<q-item-tile class="monospace ellipsis" sublabel>{{ in_tx_address_used.address }}</q-item-tile>
|
||||
|
@ -99,11 +96,6 @@
|
|||
@click.native="copyAddress(in_tx_address_used.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs.identicon.saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
|
@ -116,26 +108,17 @@
|
|||
<q-list-header class="q-px-none">Outgoing transaction sent to:</q-list-header>
|
||||
<template v-if="out_destinations">
|
||||
<q-item class="q-px-none" v-for="destination in out_destinations">
|
||||
<q-item-side>
|
||||
<Identicon :address="destination.address" ref="identicon" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile label>{{ destination.name }}</q-item-tile>
|
||||
<q-item-tile class="monospace ellipsis" sublabel>{{ destination.address }}</q-item-tile>
|
||||
<q-item-tile sublabel><FormatRyo :amount="destination.amount" /></q-item-tile>
|
||||
<q-item-tile sublabel><FormatLoki :amount="destination.amount" /></q-item-tile>
|
||||
</q-item-main>
|
||||
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay
|
||||
@click.native="copyAddress(destination.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs.identicon.saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
|
@ -143,9 +126,6 @@
|
|||
</template>
|
||||
<template v-else>
|
||||
<q-item class="q-px-none">
|
||||
<q-item-side>
|
||||
<Identicon address="" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile label>Destination unknown</q-item-tile>
|
||||
</q-item-main>
|
||||
|
@ -180,9 +160,8 @@ const { clipboard } = require("electron")
|
|||
import { mapState } from "vuex"
|
||||
import { date } from "quasar"
|
||||
const { formatDate } = date
|
||||
import Identicon from "components/identicon"
|
||||
import TxTypeIcon from "components/tx_type_icon"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
export default {
|
||||
name: "TxDetails",
|
||||
computed: mapState({
|
||||
|
@ -217,9 +196,10 @@ export default {
|
|||
let destination = this.tx.destinations[i]
|
||||
destination.name = ""
|
||||
for(j=0; j < address_book.length; j++) {
|
||||
console.log(destination.address, address_book[j].address)
|
||||
if(destination.address == address_book[j].address) {
|
||||
destination.name = address_book[j].description
|
||||
const { name, description} = address_book[j]
|
||||
const separator = description === "" ? "" : " - "
|
||||
destination.name = `${name}${separator}${description}`
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -296,9 +276,8 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
Identicon,
|
||||
TxTypeIcon,
|
||||
FormatRyo
|
||||
FormatLoki
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
<template>
|
||||
<div>
|
||||
<div class="tx-list">
|
||||
|
||||
<template v-if="tx_list_paged.length === 0">
|
||||
<p class="q-pa-md q-mb-none">No transactions found</p>
|
||||
|
@ -7,24 +7,23 @@
|
|||
|
||||
<template v-else>
|
||||
<q-infinite-scroll :handler="loadMore" ref="scroller">
|
||||
<q-list link no-border :dark="theme=='dark'" class="tx-list">
|
||||
<q-item v-for="(tx, index) in tx_list_paged" :key="tx.txid"
|
||||
<q-list link no-border :dark="theme=='dark'" class="loki-list tx-list">
|
||||
<q-item class="loki-list-item transaction" v-for="(tx, index) in tx_list_paged" :key="tx.txid"
|
||||
@click.native="details(tx)" :class="'tx-'+tx.type">
|
||||
<q-item-side>
|
||||
<TxTypeIcon :type="tx.type" />
|
||||
<q-item-side class="type">
|
||||
<div>{{ tx.type | typeToString }}</div>
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ tx.txid }}</q-item-tile>
|
||||
<q-item-tile sublabel>{{ formatHeight(tx) }}</q-item-tile>
|
||||
<q-item-main class="main">
|
||||
<q-item-tile class="amount" label>
|
||||
<FormatLoki :amount="tx.amount" />
|
||||
</q-item-tile>
|
||||
<q-item-tile sublabel>{{ tx.txid }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-item-side class="meta">
|
||||
<q-item-tile label>
|
||||
<FormatRyo :amount="tx.amount" />
|
||||
</q-item-tile>
|
||||
<q-item-tile sublabel>
|
||||
<timeago :datetime="tx.timestamp*1000" :auto-update="60">
|
||||
</timeago>
|
||||
<timeago :datetime="tx.timestamp*1000" :auto-update="60" />
|
||||
</q-item-tile>
|
||||
<q-item-tile sublabel>{{ formatHeight(tx) }}</q-item-tile>
|
||||
</q-item-side>
|
||||
|
||||
<q-context-menu>
|
||||
|
@ -64,7 +63,8 @@ import { QSpinnerDots } from "quasar"
|
|||
import Identicon from "components/identicon"
|
||||
import TxTypeIcon from "components/tx_type_icon"
|
||||
import TxDetails from "components/tx_details"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
|
||||
export default {
|
||||
name: "TxList",
|
||||
props: {
|
||||
|
@ -105,7 +105,7 @@ export default {
|
|||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
current_height: state => state.gateway.daemon.info.height,
|
||||
wallet_height: state => state.gateway.wallet.info.height,
|
||||
tx_list: state => state.gateway.wallet.transactions.tx_list
|
||||
tx_list: state => state.gateway.wallet.transactions.tx_list,
|
||||
}),
|
||||
created () {
|
||||
this.filterTxList()
|
||||
|
@ -120,8 +120,12 @@ export default {
|
|||
}
|
||||
},
|
||||
tx_list: {
|
||||
handler(val, old){
|
||||
if(val.length == old.length) return
|
||||
handler(val, old ) {
|
||||
// Check if anything changed in the tx list
|
||||
if(val.length == old.length) {
|
||||
const changed = val.filter((v, i) => v.note !== old[i].note)
|
||||
if (changed.length === 0) return
|
||||
}
|
||||
this.filterTxList()
|
||||
this.pageTxList()
|
||||
}
|
||||
|
@ -153,11 +157,52 @@ export default {
|
|||
}
|
||||
},
|
||||
},
|
||||
filters: {
|
||||
typeToString: function (value) {
|
||||
switch (value) {
|
||||
case "in":
|
||||
return "Received"
|
||||
case "out":
|
||||
return "Sent"
|
||||
case "failed":
|
||||
return "Failed"
|
||||
case "pending":
|
||||
case "pool":
|
||||
return "Pending"
|
||||
case "miner":
|
||||
return "Miner"
|
||||
case "snode":
|
||||
return "Service Node"
|
||||
case "gov":
|
||||
return "Governance"
|
||||
case "stake":
|
||||
return "Stake"
|
||||
default:
|
||||
return "-"
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
filterTxList () {
|
||||
const all_in = ["in", "pool", "miner", "snode", "gov"]
|
||||
const all_out = ["out", "pending", "stake"]
|
||||
const all_pending = ["pending", "pool"]
|
||||
this.tx_list_filtered = this.tx_list.filter((tx) => {
|
||||
let valid = true
|
||||
if(this.type !== "all" && this.type !== tx.type) {
|
||||
|
||||
if (this.type === "all_in" && !all_in.includes(tx.type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.type === "all_out" && !all_out.includes(tx.type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (this.type === "all_pending" && !all_pending.includes(tx.type)) {
|
||||
return false
|
||||
}
|
||||
|
||||
if(!this.type.startsWith("all") && this.type !== tx.type) {
|
||||
valid = false
|
||||
return valid
|
||||
}
|
||||
|
@ -236,10 +281,34 @@ export default {
|
|||
Identicon,
|
||||
TxTypeIcon,
|
||||
TxDetails,
|
||||
FormatRyo
|
||||
FormatLoki
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.tx-list {
|
||||
.loki-list-item {
|
||||
padding-top: 0;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.transaction {
|
||||
.main {
|
||||
margin: 0;
|
||||
padding: 8px 10px;
|
||||
div {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
}
|
||||
|
||||
.type {
|
||||
|
||||
div {
|
||||
min-width: 100px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
106
src/components/wallet_details.vue
Normal file
|
@ -0,0 +1,106 @@
|
|||
<template>
|
||||
<div class="column wallet-info">
|
||||
<div class="row justify-between items-center wallet-header loki-green">
|
||||
<div class="title">{{ info.name }}</div>
|
||||
<WalletSettings />
|
||||
</div>
|
||||
<div class="wallet-content">
|
||||
<div class="row justify-center">
|
||||
<div class="funds column items-center">
|
||||
<div class="balance">
|
||||
<div class="text"><span>Balance</span></div>
|
||||
<div class="value"><span><FormatLoki :amount="info.balance" /></span></div>
|
||||
</div>
|
||||
<div class="row unlocked">
|
||||
<span>Unlocked: <FormatLoki :amount="info.unlocked_balance" /></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="wallet-address row justify-center items-center">
|
||||
<div class="address">{{ info.address }}</div>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
ref="copy"
|
||||
@click="copyAddress">
|
||||
<q-tooltip anchor="center right" self="center left" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import WalletSettings from "components/wallet_settings"
|
||||
export default {
|
||||
name: "WalletDetails",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
info: state => state.gateway.wallet.info,
|
||||
}),
|
||||
methods: {
|
||||
copyAddress () {
|
||||
this.$refs.copy.$el.blur()
|
||||
clipboard.writeText(this.info.address)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Address copied to clipboard"
|
||||
})
|
||||
},
|
||||
},
|
||||
components: {
|
||||
FormatLoki,
|
||||
WalletSettings
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.wallet-info {
|
||||
.wallet-header {
|
||||
padding: 0.8rem 1.5rem;
|
||||
.title {
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-content {
|
||||
text-align: center;
|
||||
background-color: #0A0A0A;
|
||||
padding: 2em;
|
||||
|
||||
.balance {
|
||||
.text {
|
||||
font-size: 16px;
|
||||
}
|
||||
.value {
|
||||
font-size: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.wallet-address {
|
||||
margin-top: 12px;
|
||||
.address {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin: 4px 0;
|
||||
}
|
||||
.q-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
.unlocked {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
510
src/components/wallet_settings.vue
Normal file
|
@ -0,0 +1,510 @@
|
|||
<template>
|
||||
<div class="wallet-settings">
|
||||
<q-btn icon-right="more_vert" label="Settings" size="md" flat>
|
||||
<q-popover anchor="bottom right" self="top right">
|
||||
<q-list separator link>
|
||||
<q-item :disabled="!is_ready"
|
||||
v-close-overlay @click.native="getPrivateKeys()">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Show Private Keys</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
<q-item :disabled="!is_ready"
|
||||
v-close-overlay @click.native="showModal('change_password')">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Change Password</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
<q-item :disabled="!is_ready"
|
||||
v-close-overlay @click.native="showModal('rescan')">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Rescan Wallet</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
<q-item :disabled="!is_ready"
|
||||
v-close-overlay @click.native="showModal('key_image')">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Manage Key Images</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
<q-item :disabled="!is_ready"
|
||||
v-close-overlay @click.native="deleteWallet()">
|
||||
<q-item-main>
|
||||
<q-item-tile label>Delete Wallet</q-item-tile>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-popover>
|
||||
</q-btn>
|
||||
|
||||
<!-- Modals -->
|
||||
<q-modal minimized class="private-key-modal" v-model="modals.private_keys.visible" @hide="closePrivateKeys()">
|
||||
<div class="modal-header">Show private keys</div>
|
||||
<div class="q-ma-lg">
|
||||
|
||||
<template v-if="secret.mnemonic">
|
||||
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{ secret.mnemonic }}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('mnemonic', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy seed words
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="secret.view_key != secret.spend_key">
|
||||
<h6 class="q-mb-xs">View key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.view_key }}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('view_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy view key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!/^0*$/.test(secret.spend_key)">
|
||||
<h6 class="q-mb-xs">Spend key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.spend_key }}
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn
|
||||
class="copy-btn"
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy spend key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<div class="q-mt-lg">
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="hideModal('private_keys')"
|
||||
label="Close"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-modal>
|
||||
|
||||
|
||||
<q-modal minimized v-model="modals.rescan.visible">
|
||||
<div class="modal-header">Rescan wallet</div>
|
||||
<div class="q-ma-lg">
|
||||
<p>Select full rescan or rescan of spent outputs only.</p>
|
||||
|
||||
<div class="q-mt-lg">
|
||||
<q-radio v-model="modals.rescan.type" val="full" label="Rescan full blockchain" />
|
||||
</div>
|
||||
<div class="q-mt-sm">
|
||||
<q-radio v-model="modals.rescan.type" val="spent" label="Rescan spent outputs" />
|
||||
</div>
|
||||
|
||||
<div class="q-mt-xl text-right">
|
||||
<q-btn
|
||||
flat class="q-mr-sm"
|
||||
@click="hideModal('rescan')"
|
||||
label="Close"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="rescanWallet()"
|
||||
label="Rescan"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-modal>
|
||||
|
||||
<q-modal minimized v-model="modals.key_image.visible">
|
||||
<div class="modal-header">{{modals.key_image.type}} key images</div>
|
||||
<div class="q-ma-lg">
|
||||
<div class="row q-mb-md">
|
||||
<div class="q-mr-xl">
|
||||
<q-radio v-model="modals.key_image.type" val="Export" label="Export" />
|
||||
</div>
|
||||
<div>
|
||||
<q-radio v-model="modals.key_image.type" val="Import" label="Import" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-if="modals.key_image.type == 'Export'">
|
||||
<q-field style="width:450px">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-9">
|
||||
<q-input v-model="modals.key_image.export_path" stack-label="Key image export directory" disable />
|
||||
<input type="file" webkitdirectory directory id="keyImageExportPath" v-on:change="setKeyImageExportPath" ref="keyImageExportSelect" hidden />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-btn class="float-right" v-on:click="selectKeyImageExportPath">Browse</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
</template>
|
||||
<template v-if="modals.key_image.type == 'Import'">
|
||||
<q-field style="width:450px">
|
||||
<div class="row gutter-sm">
|
||||
<div class="col-9">
|
||||
<q-input v-model="modals.key_image.import_path" stack-label="Key image import file" disable />
|
||||
<input type="file" id="keyImageImportPath" v-on:change="setKeyImageImportPath" ref="keyImageImportSelect" hidden />
|
||||
</div>
|
||||
<div class="col-3">
|
||||
<q-btn class="float-right" v-on:click="selectKeyImageImportPath">Browse</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
</template>
|
||||
|
||||
<div class="q-mt-xl text-right">
|
||||
<q-btn
|
||||
flat class="q-mr-sm"
|
||||
@click="hideModal('key_image')"
|
||||
label="Close"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="doKeyImages()"
|
||||
:label="modals.key_image.type"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-modal>
|
||||
|
||||
<q-modal minimized v-model="modals.change_password.visible" @hide="clearChangePassword()">
|
||||
<div class="modal-header">Change password</div>
|
||||
<div class="q-ma-lg">
|
||||
<q-field>
|
||||
<q-input v-model="modals.change_password.old_password" type="password" float-label="Old Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="modals.change_password.new_password" type="password" float-label="New Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="modals.change_password.new_password_confirm" type="password" float-label="Confirm New Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
||||
<div class="q-mt-xl text-right">
|
||||
<q-btn
|
||||
flat class="q-mr-sm"
|
||||
@click="hideModal('change_password')"
|
||||
label="Close"
|
||||
/>
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="doChangePassword()"
|
||||
label="Change"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</q-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
|
||||
export default {
|
||||
name: "WalletSettings",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
info: state => state.gateway.wallet.info,
|
||||
secret: state => state.gateway.wallet.secret,
|
||||
data_dir: state => state.gateway.app.config.app.data_dir,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
return {
|
||||
modals: {
|
||||
private_keys: {
|
||||
visible: false,
|
||||
},
|
||||
rescan: {
|
||||
visible: false,
|
||||
type: "full",
|
||||
},
|
||||
key_image: {
|
||||
visible: false,
|
||||
type: "Export",
|
||||
export_path: "",
|
||||
import_path: "",
|
||||
},
|
||||
change_password: {
|
||||
visible: false,
|
||||
old_password: "",
|
||||
new_password: "",
|
||||
new_password_confirm: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
const path = require("path")
|
||||
this.modals.key_image.export_path = path.join(this.data_dir, "gui")
|
||||
this.modals.key_image.import_path = path.join(this.data_dir, "gui", "key_image_export")
|
||||
},
|
||||
watch: {
|
||||
secret: {
|
||||
handler(val, old) {
|
||||
if(val.view_key == old.view_key) return
|
||||
switch(this.secret.view_key) {
|
||||
case "":
|
||||
break
|
||||
case -1:
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: this.secret.mnemonic
|
||||
})
|
||||
this.$store.commit("gateway/set_wallet_data", {
|
||||
secret: {
|
||||
mnemonic: "",
|
||||
spend_key: "",
|
||||
view_key: ""
|
||||
}
|
||||
})
|
||||
break
|
||||
default:
|
||||
this.showModal("private_keys")
|
||||
break
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
showModal (which) {
|
||||
if(!this.is_ready) return
|
||||
this.modals[which].visible = true
|
||||
},
|
||||
hideModal (which) {
|
||||
this.modals[which].visible = false
|
||||
},
|
||||
copyPrivateKey (type, event) {
|
||||
event.stopPropagation()
|
||||
for(let i = 0; i < event.path.length; i++) {
|
||||
if(event.path[i].tagName == "BUTTON") {
|
||||
event.path[i].blur()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if(this.secret[type] == null) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "Error copying private key",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
clipboard.writeText(this.secret[type])
|
||||
let type_human = type.substring(0,1).toUpperCase()+type.substring(1).replace("_"," ")
|
||||
|
||||
this.$q.dialog({
|
||||
title: "Copy "+type_human,
|
||||
message: "Be careful who you send your private keys to as they control your funds.",
|
||||
ok: {
|
||||
label: "OK"
|
||||
},
|
||||
}).then(() => {
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: type_human+" copied to clipboard"
|
||||
})
|
||||
}).catch(() => {
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: type_human+" copied to clipboard"
|
||||
})
|
||||
})
|
||||
},
|
||||
getPrivateKeys () {
|
||||
if(!this.is_ready) return
|
||||
this.showPasswordConfirmation({
|
||||
title: "Show private keys",
|
||||
noPasswordMessage: "Do you want to view your private keys?",
|
||||
ok: {
|
||||
label: "SHOW"
|
||||
},
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "get_private_keys", {password})
|
||||
}).catch(() => {
|
||||
})
|
||||
},
|
||||
closePrivateKeys () {
|
||||
this.hideModal("private_keys")
|
||||
setTimeout(() => {
|
||||
this.$store.commit("gateway/set_wallet_data", {
|
||||
secret: {
|
||||
mnemonic: "",
|
||||
spend_key: "",
|
||||
view_key: ""
|
||||
}
|
||||
})
|
||||
}, 500)
|
||||
},
|
||||
rescanWallet () {
|
||||
this.hideModal("rescan")
|
||||
if(this.modals.rescan.type == "full") {
|
||||
this.$q.dialog({
|
||||
title: "Rescan wallet",
|
||||
message: "Warning: Some information about previous transactions\nsuch as the recipient's address will be lost.",
|
||||
ok: {
|
||||
label: "RESCAN"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "rescan_blockchain")
|
||||
}).catch(() => {
|
||||
})
|
||||
} else {
|
||||
this.$gateway.send("wallet", "rescan_spent")
|
||||
}
|
||||
},
|
||||
selectKeyImageExportPath () {
|
||||
this.$refs.keyImageExportSelect.click()
|
||||
},
|
||||
setKeyImageExportPath (file) {
|
||||
this.modals.key_image.export_path = file.target.files[0].path
|
||||
},
|
||||
selectKeyImageImportPath () {
|
||||
this.$refs.keyImageImportSelect.click()
|
||||
},
|
||||
setKeyImageImportPath (file) {
|
||||
this.modals.key_image.import_path = file.target.files[0].path
|
||||
},
|
||||
doKeyImages () {
|
||||
this.hideModal("key_image")
|
||||
|
||||
this.showPasswordConfirmation({
|
||||
title: this.modals.key_image.type + " key images",
|
||||
noPasswordMessage: `Do you want to ${this.modals.key_image.type.toLowerCase()} key images?`,
|
||||
ok: {
|
||||
label: this.modals.key_image.type
|
||||
},
|
||||
}).then(password => {
|
||||
if(this.modals.key_image.type == "Export")
|
||||
this.$gateway.send("wallet", "export_key_images", {password: password, path: this.modals.key_image.export_path})
|
||||
else if(this.modals.key_image.type == "Import")
|
||||
this.$gateway.send("wallet", "import_key_images", {password: password, path: this.modals.key_image.import_path})
|
||||
}).catch(() => {
|
||||
})
|
||||
|
||||
},
|
||||
doChangePassword () {
|
||||
|
||||
let old_password = this.modals.change_password.old_password
|
||||
let new_password = this.modals.change_password.new_password
|
||||
let new_password_confirm = this.modals.change_password.new_password_confirm
|
||||
|
||||
if(new_password == old_password) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "New password must be different"
|
||||
})
|
||||
} else if(new_password != new_password_confirm) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 1000,
|
||||
message: "New passwords do not match"
|
||||
})
|
||||
} else {
|
||||
this.hideModal("change_password")
|
||||
this.$gateway.send("wallet", "change_wallet_password", {old_password, new_password})
|
||||
}
|
||||
|
||||
},
|
||||
clearChangePassword () {
|
||||
this.modals.change_password.old_password = ""
|
||||
this.modals.change_password.new_password = ""
|
||||
this.modals.change_password.new_password_confirm = ""
|
||||
},
|
||||
deleteWallet () {
|
||||
this.$q.dialog({
|
||||
title: "Delete wallet",
|
||||
message: "Are you absolutely sure you want to delete your wallet?\nMake sure you have your private keys backed up.\nTHIS PROCESS IS NOT REVERSIBLE!",
|
||||
ok: {
|
||||
label: "DELETE",
|
||||
color: "red"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(() => {
|
||||
return this.hasPassword()
|
||||
}).then(hasPassword => {
|
||||
if (!hasPassword) return ""
|
||||
return this.$q.dialog({
|
||||
title: "Delete wallet",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
ok: {
|
||||
label: "DELETE",
|
||||
color: "red"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
})
|
||||
}).then(password => {
|
||||
this.$gateway.send("wallet", "delete_wallet", {password})
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.private-key-modal {
|
||||
.copy-btn {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
522
src/css/app.styl
|
@ -1,4 +1,5 @@
|
|||
// app global css
|
||||
@import '~variables'
|
||||
|
||||
@font-face {
|
||||
font-family: 'RobotoMono-Light';
|
||||
|
@ -12,16 +13,11 @@
|
|||
background: #63c9f3;
|
||||
}
|
||||
|
||||
:root {
|
||||
--q-color-primary: #497dc6;
|
||||
}
|
||||
|
||||
* {
|
||||
|
||||
scrollbar-track-color: #fff;
|
||||
scrollbar-arrow-color: #fff;
|
||||
&::-webkit-scrollbar-corner { background-color: #fff;}
|
||||
&::-webkit-scrollbar-track-piece { background-color: #fff;}
|
||||
scrollbar-track-color: #111;
|
||||
scrollbar-arrow-color: #111;
|
||||
&::-webkit-scrollbar-corner { background-color: #111;}
|
||||
&::-webkit-scrollbar-track-piece { background-color: #111;}
|
||||
|
||||
scrollbar-face-color: #646464;
|
||||
scrollbar-base-color: #646464;
|
||||
|
@ -81,7 +77,7 @@ footer,
|
|||
}
|
||||
|
||||
.q-item-sublabel {
|
||||
color: #121212;
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
.advanced-options-label .q-item-side-right {
|
||||
|
@ -89,14 +85,15 @@ footer,
|
|||
text-align: left;
|
||||
}
|
||||
|
||||
.q-layout-page {
|
||||
min-height: 0 !important;
|
||||
.q-layout {
|
||||
background: $loki-black-80;
|
||||
color:white;
|
||||
}
|
||||
|
||||
.q-layout-header {
|
||||
background: white;
|
||||
background: #0A0A0A;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #ddd;
|
||||
border-bottom: 1px solid #333;
|
||||
height: 48px;
|
||||
|
||||
.q-toolbar-title {
|
||||
|
@ -154,7 +151,10 @@ footer,
|
|||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.q-layout-page {
|
||||
min-height: 0 !important;
|
||||
}
|
||||
|
||||
.infoBox {
|
||||
|
@ -169,7 +169,7 @@ footer,
|
|||
right: 10px;
|
||||
top: 10px;
|
||||
width: 64px;
|
||||
color: #212529;
|
||||
color: #cecece;
|
||||
|
||||
svg {
|
||||
width: 60px;
|
||||
|
@ -183,7 +183,7 @@ footer,
|
|||
.text {
|
||||
font-size: 13px;
|
||||
margin-top: 8px;
|
||||
color: #212529;
|
||||
color: #cecece;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
|
@ -191,7 +191,7 @@ footer,
|
|||
font-size: 24px;
|
||||
margin-top: 4px;
|
||||
font-weight: 800;
|
||||
color: #212529;
|
||||
color: #cecece;
|
||||
font-weight:300;
|
||||
}
|
||||
}
|
||||
|
@ -213,27 +213,44 @@ footer,
|
|||
background-position: center center;
|
||||
background-repeat: no-repeat;
|
||||
background-color: #cb8fe1;
|
||||
box-shadow: inset rgba(255, 255, 255, 0.6) 0 2px 2px, inset rgba(0, 0, 0, 0.3) 0 -2px 6px;
|
||||
box-shadow: inset rgba(0, 0, 0, 0.1) 0 2px 2px, inset rgba(0, 0, 0, 0.3) 0 -2px 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.q-layout-footer.status-footer {
|
||||
border-top: 1px solid #ccc;
|
||||
background: #000000;
|
||||
color: rgba(255, 255, 255, 0.51);
|
||||
border-top: 1px solid #333;
|
||||
padding-top: 2px;
|
||||
background: #fff;
|
||||
box-shadow: none;
|
||||
font-size: 12px;
|
||||
|
||||
.status-line {
|
||||
padding: 8px;
|
||||
margin-bottom: 5px;
|
||||
|
||||
div {
|
||||
display: inline-block;
|
||||
padding: 0 8px;
|
||||
.status {
|
||||
flex: 1;
|
||||
span {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
.status-text {
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.ready {
|
||||
color: $loki-green-solid;
|
||||
}
|
||||
|
||||
.scanning, .syncing {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
float:right;
|
||||
div {
|
||||
padding: 0 8px;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,17 +261,27 @@ footer,
|
|||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
-webkit-transition: width 0.5s ease-out;
|
||||
transition: width 0.5s ease-out;
|
||||
}
|
||||
|
||||
div:first-child {
|
||||
background-color: goldenrod;
|
||||
background-color: $loki-green-dark-solid;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
background-color: green;
|
||||
background-color: $loki-green-solid;
|
||||
}
|
||||
}
|
||||
|
||||
.status-bars.syncing, .status-bars.scanning {
|
||||
div:first-child {
|
||||
background-color: #bc8f1c;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
background-color:goldenrod;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -262,219 +289,292 @@ footer,
|
|||
|
||||
.tx-list {
|
||||
|
||||
.meta {
|
||||
text-align:right;
|
||||
}
|
||||
|
||||
.q-item.tx-in,
|
||||
.q-item.tx-pool {
|
||||
.q-icon {
|
||||
color: #333;
|
||||
}
|
||||
.q-item-label {
|
||||
color: green;
|
||||
}
|
||||
&>div:last-child {
|
||||
text-align:right;
|
||||
&>div:first-child {
|
||||
span {
|
||||
color: green;
|
||||
&:before {
|
||||
content: "+";
|
||||
color: green;
|
||||
}
|
||||
}
|
||||
.q-item.tx-pool,
|
||||
.q-item.tx-miner,
|
||||
.q-item.tx-snode,
|
||||
.q-item.tx-gov {
|
||||
.amount span {
|
||||
color: #43bd43;
|
||||
&:before {
|
||||
content: "+";
|
||||
color: #43bd43;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-item.tx-stake {
|
||||
.amount span {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
|
||||
.q-item.tx-out,
|
||||
.q-item.tx-pending {
|
||||
.q-icon {
|
||||
color: #333;
|
||||
}
|
||||
.q-item-label {
|
||||
color: purple;
|
||||
}
|
||||
&>div:last-child {
|
||||
text-align:right;
|
||||
&>div:first-child {
|
||||
span {
|
||||
color: purple;
|
||||
&:before {
|
||||
content: "-";
|
||||
color: purple;
|
||||
}
|
||||
}
|
||||
.amount span {
|
||||
color: white;
|
||||
&:before {
|
||||
content: "-";
|
||||
font-weight: bold;
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-item.tx-failed {
|
||||
.amount span {
|
||||
color: orangered;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-list-dark .q-item,
|
||||
.q-item-dark .q-item,
|
||||
.q-list-header {
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
body.dark {
|
||||
.q-stepper-tab.step-waiting,
|
||||
.q-stepper-step-content{
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
&, * {
|
||||
scrollbar-track-color: #111;
|
||||
scrollbar-arrow-color: #111;
|
||||
&::-webkit-scrollbar-corner { background-color: #111;}
|
||||
&::-webkit-scrollbar-track-piece { background-color: #111;}
|
||||
}
|
||||
.q-popover {
|
||||
background: #222;
|
||||
color:#cecece;
|
||||
|
||||
.q-item-sublabel {
|
||||
color: #cecece;
|
||||
}
|
||||
.infoBoxIcon {
|
||||
color: #cecece;
|
||||
}
|
||||
|
||||
.infoBoxContent {
|
||||
.text {
|
||||
color: #cecece;
|
||||
}
|
||||
.value {
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
|
||||
.identicon {
|
||||
box-shadow: inset rgba(0, 0, 0, 0.1) 0 2px 2px, inset rgba(0, 0, 0, 0.3) 0 -2px 6px;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
|
||||
.q-layout {
|
||||
background: #111;
|
||||
color:#cecece;
|
||||
|
||||
|
||||
.q-layout-header {
|
||||
background: #111;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
.q-layout-footer.status-footer {
|
||||
background: #111;
|
||||
color: #cecece;
|
||||
.q-list-separator > .q-item-division + .q-item-division, .q-item-division + .q-item-separator {
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
}
|
||||
|
||||
.q-list-dark .q-item,
|
||||
.q-item-dark .q-item,
|
||||
.q-list-header {
|
||||
color: #cecece;
|
||||
.header-popover.q-popover {
|
||||
background: $primary;
|
||||
color: white;
|
||||
|
||||
.q-list-separator > .q-item-division + .q-item-division, .q-item-division + .q-item-separator {
|
||||
border-top: 1px solid $loki-green-dark-solid;
|
||||
}
|
||||
}
|
||||
|
||||
.q-stepper-tab.step-waiting,
|
||||
.q-stepper-step-content{
|
||||
color: #cecece;
|
||||
.modal.minimized {
|
||||
.q-input {
|
||||
.q-input-target {
|
||||
color: #cecece;
|
||||
}
|
||||
&.q-if:hover:before {
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
.q-popover {
|
||||
background: #222;
|
||||
.modal-content,
|
||||
.modal-body {
|
||||
background: $loki-black-80;
|
||||
color:#cecece;
|
||||
|
||||
.q-list-separator > .q-item-division + .q-item-division, .q-item-division + .q-item-separator {
|
||||
border-top: 1px solid #333;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.modal.minimized {
|
||||
.q-input {
|
||||
.q-input-target {
|
||||
color: #cecece;
|
||||
}
|
||||
&.q-if:hover:before {
|
||||
.q-layout-header {
|
||||
|
||||
.q-toolbar {
|
||||
&>* {
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
||||
.modal-content,
|
||||
.modal-body {
|
||||
background: #111;
|
||||
color:#cecece;
|
||||
}
|
||||
|
||||
.q-layout-header {
|
||||
background: #111;
|
||||
box-shadow: none;
|
||||
border-bottom: 1px solid #333;
|
||||
|
||||
.q-toolbar {
|
||||
&>* {
|
||||
color: #cecece;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.tx-list {
|
||||
|
||||
.q-item.tx-in,
|
||||
.q-item.tx-pool {
|
||||
.q-icon {
|
||||
color: #cecece;
|
||||
}
|
||||
.q-item-label {
|
||||
color: lightgreen;
|
||||
}
|
||||
&>div:last-child {
|
||||
text-align:right;
|
||||
&>div:first-child {
|
||||
span {
|
||||
color: lightgreen;
|
||||
&:before {
|
||||
content: "+";
|
||||
color: lightgreen;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-item.tx-out,
|
||||
.q-item.tx-pending {
|
||||
.q-icon {
|
||||
color: #cecece;
|
||||
}
|
||||
.q-item-label {
|
||||
color: mediumpurple;
|
||||
}
|
||||
&>div:last-child {
|
||||
text-align:right;
|
||||
&>div:first-child {
|
||||
span {
|
||||
color: mediumpurple;
|
||||
&:before {
|
||||
content: "-";
|
||||
color: mediumpurple;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-layout-footer.status-footer {
|
||||
|
||||
.status-bars {
|
||||
|
||||
div:first-child {
|
||||
background-color: goldenrod;
|
||||
}
|
||||
|
||||
div:last-child {
|
||||
background-color: #497dc6;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.loki-green {
|
||||
background: $loki-green;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.startup-icons {
|
||||
.solid {
|
||||
color:$loki-green-solid;
|
||||
g,path {
|
||||
fill: $loki-green-solid;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.hr-separator {
|
||||
height: 2px;
|
||||
background: $secondary;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.navigation {
|
||||
.q-btn {
|
||||
color: white;
|
||||
background: $loki-black-90;
|
||||
}
|
||||
|
||||
.router-link-exact-active > .q-btn {
|
||||
background: $loki-green;
|
||||
}
|
||||
|
||||
}
|
||||
.wallet-list {
|
||||
.q-item-label {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.q-item {
|
||||
background: $secondary;
|
||||
.wallet-icon {
|
||||
color: $tertiary;
|
||||
g,path {
|
||||
fill: $tertiary;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.q-item:hover, .q-item.selected {
|
||||
background: $primary !important;
|
||||
|
||||
.wallet-icon {
|
||||
color:$loki-green-solid;
|
||||
g,path {
|
||||
fill: $loki-green-solid;
|
||||
}
|
||||
}
|
||||
|
||||
.q-icon {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.q-item-sublabel {
|
||||
color: white
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.receive {
|
||||
.q-list-header {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
.q-item-sublabel {
|
||||
color: $loki-black-50;
|
||||
}
|
||||
|
||||
.q-item-separator-component {
|
||||
background-color: $secondary;
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
.primary-address {
|
||||
background: #3eb13e !important;
|
||||
|
||||
.q-item, .q-item-side {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.q-item-sublabel {
|
||||
color: rgba(255,255,255,0.9);
|
||||
}
|
||||
}
|
||||
|
||||
.primary-address:hover {
|
||||
background: $loki-green-solid !important;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.loki-list {
|
||||
.loki-list-item {
|
||||
background: #313131;
|
||||
-webkit-transition: background-color 0.2s ease-in;
|
||||
transition: background-color 0.2s ease-in;
|
||||
|
||||
margin: 0 16px;
|
||||
border-radius: 3px;
|
||||
|
||||
+ .loki-list-item {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.loki-list-item:hover {
|
||||
background: rgba(117,117,117,0.3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.tx-list {
|
||||
.transaction .main {
|
||||
border-left: 1px solid #252525;
|
||||
}
|
||||
}
|
||||
|
||||
.loki-field {
|
||||
.content {
|
||||
border: 1px solid #484848;
|
||||
|
||||
-webkit-transition: background-color 0.2s ease-in, border-color 0.2s ease-in;
|
||||
transition: background-color 0.2s ease-in, border-color 0.2s ease-in;
|
||||
}
|
||||
|
||||
&:not(.disable):not(.disable-hover) {
|
||||
.content:hover {
|
||||
background: #2e2e2e;
|
||||
}
|
||||
}
|
||||
|
||||
&.disable {
|
||||
.content {
|
||||
border-color: #404040 !important;
|
||||
}
|
||||
}
|
||||
|
||||
.content.error {
|
||||
border-color: red;
|
||||
}
|
||||
|
||||
.label {
|
||||
color: white;
|
||||
|
||||
.optional {
|
||||
color: #7C7C7C;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-node-staking {
|
||||
.address-type {
|
||||
color: $loki-green-solid;
|
||||
&.not-ours {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.service-node-registration {
|
||||
.description{
|
||||
color: #b7b7b7;
|
||||
font-style: normal;
|
||||
b {
|
||||
color: white;
|
||||
font-style: italic;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.welcome {
|
||||
.q-layout-footer {
|
||||
background: $secondary
|
||||
}
|
||||
}
|
||||
|
||||
.address-book, .address-header {
|
||||
.q-item-label {
|
||||
color: white;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,12 +14,21 @@
|
|||
// It"s highly recommended to change the default colors
|
||||
// to match your app"s branding.
|
||||
|
||||
$primary = #027be3
|
||||
$secondary = #26A69A
|
||||
$tertiary = #555
|
||||
$primary = $loki-green
|
||||
$secondary = $loki-black-90
|
||||
$tertiary = $loki-black-80
|
||||
|
||||
$neutral = #E0E1E2
|
||||
$positive = #21BA45
|
||||
$negative = #DB2828
|
||||
$info = #31CCEC
|
||||
$warning = #F2C037
|
||||
|
||||
$loki-green = linear-gradient(180deg, #419B41 0%, #43BD43 100%)
|
||||
$loki-green-solid = #5BCA5B;
|
||||
$loki-green-dark-solid = #419B41;
|
||||
|
||||
$loki-black-90 = #0A0A0A
|
||||
$loki-black-80 = #252525
|
||||
$loki-black-60 = #313131
|
||||
$loki-black-50 = #7E7E7E;
|
||||
|
|
|
@ -22,70 +22,70 @@
|
|||
SOFTWARE.
|
||||
*/
|
||||
|
||||
const crypto = require("crypto");
|
||||
const crypto = require("crypto")
|
||||
|
||||
const ALGORITHM_NAME = "aes-128-gcm";
|
||||
const ALGORITHM_NONCE_SIZE = 12;
|
||||
const ALGORITHM_TAG_SIZE = 16;
|
||||
const ALGORITHM_KEY_SIZE = 16;
|
||||
const PBKDF2_NAME = "sha256";
|
||||
const PBKDF2_SALT_SIZE = 16;
|
||||
const PBKDF2_ITERATIONS = 32767;
|
||||
const ALGORITHM_NAME = "aes-128-gcm"
|
||||
const ALGORITHM_NONCE_SIZE = 12
|
||||
const ALGORITHM_TAG_SIZE = 16
|
||||
const ALGORITHM_KEY_SIZE = 16
|
||||
const PBKDF2_NAME = "sha256"
|
||||
const PBKDF2_SALT_SIZE = 16
|
||||
const PBKDF2_ITERATIONS = 32767
|
||||
|
||||
export class SCEE {
|
||||
encryptString(plaintext, password) {
|
||||
encryptString (plaintext, password) {
|
||||
// Generate a 128-bit salt using a CSPRNG.
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE);
|
||||
let salt = crypto.randomBytes(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive a key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Encrypt and prepend salt.
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ]);
|
||||
let ciphertextAndNonceAndSalt = Buffer.concat([ salt, this.encrypt(new Buffer(plaintext, "utf8"), key) ])
|
||||
|
||||
// Return as base64 string.
|
||||
return ciphertextAndNonceAndSalt.toString("base64");
|
||||
return ciphertextAndNonceAndSalt.toString("base64")
|
||||
}
|
||||
|
||||
decryptString(base64CiphertextAndNonceAndSalt, password) {
|
||||
decryptString (base64CiphertextAndNonceAndSalt, password) {
|
||||
// Decode the base64.
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64");
|
||||
let ciphertextAndNonceAndSalt = new Buffer(base64CiphertextAndNonceAndSalt, "base64")
|
||||
|
||||
// Create buffers of salt and ciphertextAndNonce.
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE);
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE);
|
||||
let salt = ciphertextAndNonceAndSalt.slice(0, PBKDF2_SALT_SIZE)
|
||||
let ciphertextAndNonce = ciphertextAndNonceAndSalt.slice(PBKDF2_SALT_SIZE)
|
||||
|
||||
// Derive the key using PBKDF2.
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME);
|
||||
let key = crypto.pbkdf2Sync(new Buffer(password, "utf8"), salt, PBKDF2_ITERATIONS, ALGORITHM_KEY_SIZE, PBKDF2_NAME)
|
||||
|
||||
// Decrypt and return result.
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8");
|
||||
return this.decrypt(ciphertextAndNonce, key).toString("utf8")
|
||||
}
|
||||
|
||||
encrypt(plaintext, key) {
|
||||
encrypt (plaintext, key) {
|
||||
// Generate a 96-bit nonce using a CSPRNG.
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE);
|
||||
let nonce = crypto.randomBytes(ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createCipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Encrypt and prepend nonce.
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ]);
|
||||
let ciphertext = Buffer.concat([ cipher.update(plaintext), cipher.final() ])
|
||||
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ]);
|
||||
return Buffer.concat([ nonce, ciphertext, cipher.getAuthTag() ])
|
||||
}
|
||||
|
||||
decrypt(ciphertextAndNonce, key) {
|
||||
decrypt (ciphertextAndNonce, key) {
|
||||
// Create buffers of nonce, ciphertext and tag.
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE);
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE);
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE);
|
||||
let nonce = ciphertextAndNonce.slice(0, ALGORITHM_NONCE_SIZE)
|
||||
let ciphertext = ciphertextAndNonce.slice(ALGORITHM_NONCE_SIZE, ciphertextAndNonce.length - ALGORITHM_TAG_SIZE)
|
||||
let tag = ciphertextAndNonce.slice(ciphertext.length + ALGORITHM_NONCE_SIZE)
|
||||
|
||||
// Create the cipher instance.
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce);
|
||||
let cipher = crypto.createDecipheriv(ALGORITHM_NAME, key, nonce)
|
||||
|
||||
// Decrypt and return result.
|
||||
cipher.setAuthTag(tag);
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ]);
|
||||
cipher.setAuthTag(tag)
|
||||
return Buffer.concat([ cipher.update(ciphertext), cipher.final() ])
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,26 +1,25 @@
|
|||
import { ipcRenderer } from "electron"
|
||||
import { Notify, Dialog, Loading, LocalStorage } from "quasar"
|
||||
import { SCEE } from "./SCEE-Node";
|
||||
import * as WebSocket from "ws"
|
||||
|
||||
export class Gateway {
|
||||
|
||||
constructor(app, router) {
|
||||
import { EventEmitter } from "events"
|
||||
import { SCEE } from "./SCEE-Node"
|
||||
|
||||
export class Gateway extends EventEmitter {
|
||||
constructor (app, router) {
|
||||
super()
|
||||
this.app = app
|
||||
this.router = router
|
||||
this.token = null
|
||||
this.scee = new SCEE()
|
||||
|
||||
let theme = LocalStorage.has("theme") ? LocalStorage.get.item("theme") : "light"
|
||||
let theme = LocalStorage.has("theme") ? LocalStorage.get.item("theme") : "dark"
|
||||
this.app.store.commit("gateway/set_app_data", {
|
||||
config: {
|
||||
appearance: {
|
||||
theme
|
||||
}
|
||||
}
|
||||
});
|
||||
this.app.store.watch( state => state.gateway.app.config.appearance.theme, (theme) => {
|
||||
})
|
||||
this.app.store.watch(state => state.gateway.app.config.appearance.theme, (theme) => {
|
||||
LocalStorage.set("theme", theme)
|
||||
})
|
||||
|
||||
|
@ -30,34 +29,33 @@ export class Gateway {
|
|||
status: {
|
||||
code: 1 // Connecting to backend
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
ipcRenderer.on("initialize", (event, data) => {
|
||||
this.token = data.token
|
||||
setTimeout(() => {
|
||||
this.ws = new WebSocket("ws://127.0.0.1:"+data.port);
|
||||
this.ws.on("open", () => {this.open()});
|
||||
this.ws.on("message", (message) => {this.receive(message)});
|
||||
}, 1000);
|
||||
});
|
||||
this.ws = new WebSocket("ws://127.0.0.1:" + data.port)
|
||||
this.ws.addEventListener("open", () => { this.open() })
|
||||
this.ws.addEventListener("message", (e) => { this.receive(e.data) })
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
ipcRenderer.on("confirmClose", () => {
|
||||
this.confirmClose("Are you sure you want to exit?")
|
||||
});
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
open() {
|
||||
open () {
|
||||
this.app.store.commit("gateway/set_app_data", {
|
||||
status: {
|
||||
code: 2 // Loading config
|
||||
}
|
||||
});
|
||||
this.send("core", "init");
|
||||
})
|
||||
this.send("core", "init")
|
||||
}
|
||||
|
||||
confirmClose(msg) {
|
||||
if(this.closeDialog) {
|
||||
confirmClose (msg) {
|
||||
if (this.closeDialog) {
|
||||
return
|
||||
}
|
||||
this.closeDialog = true
|
||||
|
@ -70,7 +68,7 @@ export class Gateway {
|
|||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.app.store.state.gateway.app.config.appearance.theme=="dark"?"white":"dark"
|
||||
color: this.app.store.state.gateway.app.config.appearance.theme == "dark" ? "white" : "dark"
|
||||
}
|
||||
}).then(() => {
|
||||
this.closeDialog = false
|
||||
|
@ -80,76 +78,88 @@ export class Gateway {
|
|||
}).catch(() => {
|
||||
this.closeDialog = false
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
send(module, method, data={}) {
|
||||
send (module, method, data = {}) {
|
||||
let message = {
|
||||
module,
|
||||
method,
|
||||
data
|
||||
}
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token);
|
||||
this.ws.send(encrypted_data);
|
||||
|
||||
let encrypted_data = this.scee.encryptString(JSON.stringify(message), this.token)
|
||||
this.ws.send(encrypted_data)
|
||||
}
|
||||
|
||||
receive(message) {
|
||||
|
||||
receive (message) {
|
||||
// should wrap this in a try catch, and if fail redirect to error screen
|
||||
// shouldn't happen outside of dev environment
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(message, this.token));
|
||||
let decrypted_data = JSON.parse(this.scee.decryptString(message, this.token))
|
||||
|
||||
if (typeof decrypted_data !== "object" ||
|
||||
!decrypted_data.hasOwnProperty("event") ||
|
||||
!decrypted_data.hasOwnProperty("data"))
|
||||
return
|
||||
!decrypted_data.hasOwnProperty("data")) { return }
|
||||
|
||||
switch (decrypted_data.event) {
|
||||
case "set_has_password":
|
||||
this.emit("has_password", decrypted_data.data)
|
||||
break
|
||||
case "set_valid_address":
|
||||
this.emit("validate_address", decrypted_data.data)
|
||||
break
|
||||
case "set_app_data":
|
||||
this.app.store.commit("gateway/set_app_data", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_app_data":
|
||||
this.app.store.commit("gateway/set_app_data", decrypted_data.data)
|
||||
break
|
||||
case "set_daemon_data":
|
||||
this.app.store.commit("gateway/set_daemon_data", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_daemon_data":
|
||||
this.app.store.commit("gateway/set_daemon_data", decrypted_data.data)
|
||||
break
|
||||
case "set_wallet_data":
|
||||
case "set_wallet_error":
|
||||
this.app.store.commit("gateway/set_wallet_data", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "set_wallet_data":
|
||||
case "set_wallet_error":
|
||||
this.app.store.commit("gateway/set_wallet_data", decrypted_data.data)
|
||||
break
|
||||
case "reset_wallet_error":
|
||||
this.app.store.dispatch("gateway/resetWalletStatus")
|
||||
break
|
||||
|
||||
case "set_tx_status":
|
||||
this.app.store.commit("gateway/set_tx_status", decrypted_data.data)
|
||||
break
|
||||
case "set_tx_status":
|
||||
this.app.store.commit("gateway/set_tx_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "wallet_list":
|
||||
this.app.store.commit("gateway/set_wallet_list", decrypted_data.data)
|
||||
break
|
||||
case "set_snode_status":
|
||||
this.app.store.commit("gateway/set_snode_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "settings_changed_reboot":
|
||||
this.confirmClose("Changes require restart. Would you like to exit now?")
|
||||
break
|
||||
case "set_old_gui_import_status":
|
||||
this.app.store.commit("gateway/set_old_gui_import_status", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "show_notification":
|
||||
let notification = {
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: ""
|
||||
}
|
||||
Notify.create(Object.assign(notification, decrypted_data.data))
|
||||
break
|
||||
case "wallet_list":
|
||||
this.app.store.commit("gateway/set_wallet_list", decrypted_data.data)
|
||||
break
|
||||
|
||||
case "return_to_wallet_select":
|
||||
this.router.replace({ path: "/wallet-select" })
|
||||
setTimeout(() => {
|
||||
// short delay to prevent wallet data reaching the
|
||||
// websocket moments after we close and reset data
|
||||
this.app.store.dispatch("gateway/resetWalletData")
|
||||
}, 250);
|
||||
break
|
||||
case "settings_changed_reboot":
|
||||
this.confirmClose("Changes require restart. Would you like to exit now?")
|
||||
break
|
||||
|
||||
case "show_notification":
|
||||
let notification = {
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: ""
|
||||
}
|
||||
Notify.create(Object.assign(notification, decrypted_data.data))
|
||||
break
|
||||
|
||||
case "return_to_wallet_select":
|
||||
this.router.replace({ path: "/wallet-select" })
|
||||
setTimeout(() => {
|
||||
// short delay to prevent wallet data reaching the
|
||||
// websocket moments after we close and reset data
|
||||
this.app.store.dispatch("gateway/resetWalletData")
|
||||
}, 250)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -11,9 +11,9 @@
|
|||
@click="cancel()" />
|
||||
</template>
|
||||
|
||||
<q-toolbar-title v-if="page_title=='Ryo'">
|
||||
<div style="margin-top:7px">
|
||||
<img src="statics/ryo-wallet.svg" height="32">
|
||||
<q-toolbar-title v-if="page_title=='Loki'">
|
||||
<div class="flex items-center justify-center" style="margin-top:7px">
|
||||
<img src="statics/loki.svg" height="32">
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
<q-toolbar-title v-else>
|
||||
|
@ -59,12 +59,14 @@ export default {
|
|||
return "Restore view-only wallet"
|
||||
case "wallet-import-legacy":
|
||||
return "Import wallet from legacy gui"
|
||||
case "wallet-import-old-gui":
|
||||
return "Import wallets from old GUI"
|
||||
case "wallet-created":
|
||||
return "Wallet created/restored"
|
||||
|
||||
default:
|
||||
case "wallet-select":
|
||||
return "Ryo"
|
||||
return "Loki"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
@ -2,29 +2,62 @@
|
|||
<q-layout view="hHh Lpr lFf">
|
||||
<q-layout-header class="shift-title">
|
||||
<main-menu />
|
||||
|
||||
<q-tabs class="col" align="justify" :color="theme == 'dark' ? 'light' : 'dark'" inverted>
|
||||
|
||||
<q-route-tab to="/wallet" default slot="title">
|
||||
<span><q-icon name="attach_money" /> Wallet</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/receive" slot="title">
|
||||
<span><q-icon name="call_received" /> Receive</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/send" slot="title">
|
||||
<span><q-icon name="call_made" /> Send</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/addressbook" slot="title">
|
||||
<span><q-icon name="person" /> Address Book</span>
|
||||
</q-route-tab>
|
||||
<q-route-tab to="/wallet/txhistory" slot="title">
|
||||
<span><q-icon name="history" /> TX History</span>
|
||||
</q-route-tab>
|
||||
|
||||
</q-tabs>
|
||||
<q-toolbar-title>
|
||||
<div class="flex items-center justify-center" style="margin-top:7px">
|
||||
<img src="statics/loki.svg" height="32">
|
||||
</div>
|
||||
</q-toolbar-title>
|
||||
</q-layout-header>
|
||||
|
||||
<q-page-container>
|
||||
<!-- <AddressHeader :address="info.address" :title="info.name" /> -->
|
||||
<WalletDetails />
|
||||
|
||||
<div class="navigation row items-end">
|
||||
<router-link to="/wallet">
|
||||
<q-btn
|
||||
class="single-icon"
|
||||
size="md"
|
||||
icon="swap_horiz"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link to="/wallet/send">
|
||||
<q-btn
|
||||
class="large-btn"
|
||||
label="Send"
|
||||
size="md"
|
||||
icon-right="arrow_right_alt"
|
||||
align="left"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link to="/wallet/receive">
|
||||
<q-btn
|
||||
class="large-btn"
|
||||
label="Receive"
|
||||
size="md"
|
||||
icon-right="save_alt"
|
||||
align="left"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link to="/wallet/servicenode">
|
||||
<q-btn
|
||||
class="large-btn"
|
||||
label="Service node"
|
||||
size="md"
|
||||
icon-right="router"
|
||||
align="left"
|
||||
/>
|
||||
</router-link>
|
||||
<router-link to="/wallet/addressbook" class="address">
|
||||
<q-btn
|
||||
class="single-icon"
|
||||
size="md"
|
||||
icon="person"
|
||||
/>
|
||||
</router-link>
|
||||
|
||||
</div>
|
||||
<div class="hr-separator" />
|
||||
<keep-alive>
|
||||
<router-view />
|
||||
</keep-alive>
|
||||
|
@ -36,14 +69,18 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
import { openURL } from "quasar"
|
||||
import { mapState } from "vuex"
|
||||
import WalletDetails from "components/wallet_details"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import StatusFooter from "components/footer"
|
||||
import MainMenu from "components/mainmenu"
|
||||
export default {
|
||||
name: "LayoutDefault",
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
info: state => state.gateway.wallet.info,
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
|
@ -55,10 +92,41 @@ export default {
|
|||
},
|
||||
components: {
|
||||
StatusFooter,
|
||||
MainMenu
|
||||
MainMenu,
|
||||
WalletDetails
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.navigation {
|
||||
padding: 8px 12px;
|
||||
|
||||
> * {
|
||||
margin: 2px 0;
|
||||
margin-right: 12px;
|
||||
}
|
||||
|
||||
> *:last-child {
|
||||
margin-right: 0px;
|
||||
}
|
||||
|
||||
.address {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.single-icon {
|
||||
width: 38px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.large-btn {
|
||||
width: 160px;
|
||||
.q-btn-inner > *:last-child {
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
||||
|
|
38
src/mixins/wallet_password.js
Normal file
|
@ -0,0 +1,38 @@
|
|||
import { mapState } from "vuex"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme
|
||||
}),
|
||||
methods: {
|
||||
hasPassword () {
|
||||
// Validate the address
|
||||
return new Promise((resolve) => {
|
||||
this.$gateway.once("has_password", (data) => {
|
||||
resolve(!!data)
|
||||
})
|
||||
this.$gateway.send("wallet", "has_password")
|
||||
})
|
||||
},
|
||||
|
||||
showPasswordConfirmation (options) {
|
||||
const { noPasswordMessage, ...other } = options
|
||||
|
||||
return this.hasPassword().then(hasPassword => {
|
||||
return this.$q.dialog({
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme === "dark" ? "white" : "dark"
|
||||
},
|
||||
...other,
|
||||
message: hasPassword ? "Enter wallet password to continue." : noPasswordMessage,
|
||||
prompt: hasPassword ? {
|
||||
model: "",
|
||||
type: "password"
|
||||
} : null
|
||||
})
|
||||
}).then(password => password || "")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,32 +2,7 @@
|
|||
<q-page>
|
||||
<div class="init-screen-page text-center">
|
||||
<div class="absolute-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="640" viewBox="0 0 859.4011 184.7379">
|
||||
<defs>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#323232"/>
|
||||
<stop offset="1" stop-color="#b4b4b4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#62c9f3"/>
|
||||
<stop offset="1" stop-color="#3d58b0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="h" x1="341.1354" x2="341.1354" y1="186.9957" y2="128.2946" gradientTransform="translate(69.6895 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
|
||||
<linearGradient id="c" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="e" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="f" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="g" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="i" x1="341.1354" x2="341.1354" y1="186.9957" y2="128.2946" gradientTransform="translate(69.6895 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
|
||||
</defs>
|
||||
<path fill="url(#c)" d="M41.7535 41.89H75.105v33.0785H41.7535zm-4.2333-4.2334v41.5453h41.8181V37.6565zM58.4295 4.2333c29.9566 0 54.1957 24.2396 54.1957 54.1962 0 29.9566-24.239 54.1957-54.1957 54.1957-29.9566 0-54.1962-24.239-54.1962-54.1957 0-29.9566 24.2396-54.1962 54.1962-54.1962zm0-4.2333C26.185 0 0 26.185 0 58.4295s26.185 58.429 58.4295 58.429 58.429-26.1845 58.429-58.429S90.674 0 58.4295 0z"/>
|
||||
<g fill="url(#d)" transform="translate(12 -183.1415)">
|
||||
<path fill="url(#e)" d="M191.1345 287.4374l-15.7334-39.4667q-1.7333.2667-5.4666.2667h-34v39.2h-4.8v-91.7333h40.8q10.5333 0 15.7333 3.6 5.2 3.6 6.5333 9.0666 1.3334 5.3334 1.3334 13.6 0 6.6667-.9334 11.4667-.9333 4.6667-4.2666 8.4-3.3334 3.7333-10 5.3333l16.1333 40.2667zm-21.4667-43.7333q9.3334 0 13.8667-2.6667 4.5333-2.6667 5.8666-7.0667 1.3334-4.5333 1.3334-12 0-7.6-1.2-12-1.0667-4.4-5.2-7.0666-4-2.6667-12.6667-2.6667h-35.7333v43.4667zm63.6625 43.7333v-37.4667l-34.8-54.2666h5.6l31.2 49.2h.9333l31.3334-49.2h5.4666l-34.9333 54.2666v37.4667zm79.7979 1.0666q-17.8666 0-25.0666-3.0666-7.2-3.0667-9.3334-12.1333-2-9.0667-2-31.7334 0-22.6666 2-31.7333 2.1334-9.0666 9.3334-12.1333 7.2-3.0667 25.0666-3.0667 17.8667 0 25.0667 3.0667 7.2 3.0666 9.2 12.1333 2.1333 9.0667 2.1333 31.7333 0 22.6667-2.1333 31.7334-2 9.0666-9.2 12.1333-7.2 3.0667-25.0667 3.0667zm0-4.5333q16.1334 0 21.8667-2 5.8667-2.1333 7.7333-10.2666 2-8.2667 2-30.1334 0-21.8666-2-30-1.8666-8.2666-7.7333-10.2666-5.7333-2.1334-21.8667-2.1334-16.1333 0-22 2.1334-5.7333 2-7.7333 10.2666-1.8667 8.1334-1.8667 30 0 21.8667 1.8667 30.1334 2 8.1333 7.7333 10.2666 5.8667 2 22 2zm153.3563 3.4667l-27.0667-83.0667h-.5333l-27.0667 83.0667h-4.6666l-27.2-91.7333h5.2l24.1333 83.0666h.8l26.6667-83.0666h5.0666l26.6667 83.0666h.8l24.1333-83.0666h4.9333l-27.2 91.7333zm104.6416 0l-12.1333-29.6h-45.8667l-12.1333 29.6h-5.0667l37.8667-91.7333h4.6667l37.8666 91.7333zm-34.6666-84.9333h-.6667l-20.8 50.8h42.1333zm51.6125 84.9333v-91.7333h4.8v87.2h46.6666v4.5333zm62.3541 0v-91.7333h4.8v87.2h46.6667v4.5333z"/>
|
||||
<path fill="url(#f)" d="M712.7803 287.4374v-91.7333h56.8v4.5333h-52v38.5333h45.7333v4.5334h-45.7333v39.6h52v4.5333z" letter-spacing="-2"/>
|
||||
<path fill="url(#g)" d="M811.5345 287.4374v-87.2h-31.0667v-4.5333h66.9333v4.5333h-31.0666v87.2z" font-size="133.3333"/>
|
||||
</g>
|
||||
<path fill="url(#i)" d="M359.2174 367.2394l-7.28-17.76h-27.52l-7.28 17.76h-3.04l22.72-55.04h2.8l22.72 55.04zm-20.8-50.96h-.4l-12.48 30.48h25.28zm41.0388 50.96v-52.32h-18.64v-2.72h40.16v2.72h-18.64v52.32zm53.7625.64q-10.72 0-15.04-1.84-4.32-1.84-5.6-7.28-1.2-5.44-1.2-19.04 0-13.6 1.2-19.04 1.28-5.44 5.6-7.28 4.32-1.84 15.04-1.84 10.72 0 15.04 1.84 4.32 1.84 5.52 7.28 1.28 5.44 1.28 19.04 0 13.6-1.28 19.04-1.2 5.44-5.52 7.28-4.32 1.84-15.04 1.84zm0-2.72q9.68 0 13.12-1.2 3.52-1.28 4.64-6.16 1.2-4.96 1.2-18.08 0-13.12-1.2-18-1.12-4.96-4.64-6.16-3.44-1.28-13.12-1.28t-13.2 1.28q-3.44 1.2-4.64 6.16-1.12 4.88-1.12 18 0 13.12 1.12 18.08 1.2 4.88 4.64 6.16 3.52 1.2 13.2 1.2zm90.205 2.08v-49.44h-.32l-22.96 49.44h-2.56l-22.96-49.44h-.32v49.44h-2.8v-55.04h3.68l23.52 50.8h.4l23.6-50.8h3.6v55.04z" transform="translate(9.5 -183.1415)"/>
|
||||
</svg>
|
||||
<img src="statics/loki.svg" width="400" class="q-mb-md">
|
||||
|
||||
<div class="startup-icons q-mt-xl q-mb-lg">
|
||||
<div ref="backend">
|
||||
|
@ -106,7 +81,7 @@ export default {
|
|||
this.$q.notify({
|
||||
type: "warning",
|
||||
timeout: 2000,
|
||||
message: "Warning: ryod not found, using remote node"
|
||||
message: "Warning: lokid not found, using remote node"
|
||||
})
|
||||
break;
|
||||
case 6:
|
||||
|
@ -149,50 +124,21 @@ export default {
|
|||
&>div {
|
||||
display: inline-block;
|
||||
margin: 0 15px;
|
||||
color: lightgrey;
|
||||
color: #444;
|
||||
g,path {
|
||||
fill: lightgrey;
|
||||
}
|
||||
}
|
||||
.solid {
|
||||
color: var(--q-color-primary);
|
||||
g,path {
|
||||
fill: var(--q-color-primary);
|
||||
fill: #444;
|
||||
}
|
||||
}
|
||||
.pulse {
|
||||
color:black;
|
||||
color:#cecece;
|
||||
opacity: 0.6;
|
||||
animation: fade 2s infinite;
|
||||
g,path {
|
||||
fill:black;
|
||||
}
|
||||
}
|
||||
}
|
||||
.dark {
|
||||
.startup-icons {
|
||||
&>div {
|
||||
display: inline-block;
|
||||
margin: 0 15px;
|
||||
color: #444;
|
||||
g,path {
|
||||
fill: #444;
|
||||
}
|
||||
}
|
||||
.solid {
|
||||
color: var(--q-color-primary);
|
||||
g,path {
|
||||
fill: var(--q-color-primary);
|
||||
}
|
||||
}
|
||||
.pulse {
|
||||
color: #cecece;
|
||||
g,path {
|
||||
fill: #cecece;
|
||||
}
|
||||
fill:#cecece;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fade {
|
||||
0%,100% { opacity: 0.3 }
|
||||
50% { opacity: 0.6 }
|
||||
|
|
|
@ -2,32 +2,7 @@
|
|||
<q-page>
|
||||
<div class="init-screen-page text-center">
|
||||
<div class="absolute-center">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="640" viewBox="0 0 859.4011 184.7379">
|
||||
<defs>
|
||||
<linearGradient id="b">
|
||||
<stop offset="0" stop-color="#323232"/>
|
||||
<stop offset="1" stop-color="#b4b4b4"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="a" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#62c9f3"/>
|
||||
<stop offset="1" stop-color="#3d58b0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="d" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="h" x1="341.1354" x2="341.1354" y1="186.9957" y2="128.2946" gradientTransform="translate(69.6895 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
|
||||
<linearGradient id="c" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="e" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="f" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="g" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="i" x1="341.1354" x2="341.1354" y1="186.9957" y2="128.2946" gradientTransform="translate(69.6895 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#b"/>
|
||||
</defs>
|
||||
<path fill="url(#c)" d="M41.7535 41.89H75.105v33.0785H41.7535zm-4.2333-4.2334v41.5453h41.8181V37.6565zM58.4295 4.2333c29.9566 0 54.1957 24.2396 54.1957 54.1962 0 29.9566-24.239 54.1957-54.1957 54.1957-29.9566 0-54.1962-24.239-54.1962-54.1957 0-29.9566 24.2396-54.1962 54.1962-54.1962zm0-4.2333C26.185 0 0 26.185 0 58.4295s26.185 58.429 58.4295 58.429 58.429-26.1845 58.429-58.429S90.674 0 58.4295 0z"/>
|
||||
<g fill="url(#d)" transform="translate(12 -183.1415)">
|
||||
<path fill="url(#e)" d="M191.1345 287.4374l-15.7334-39.4667q-1.7333.2667-5.4666.2667h-34v39.2h-4.8v-91.7333h40.8q10.5333 0 15.7333 3.6 5.2 3.6 6.5333 9.0666 1.3334 5.3334 1.3334 13.6 0 6.6667-.9334 11.4667-.9333 4.6667-4.2666 8.4-3.3334 3.7333-10 5.3333l16.1333 40.2667zm-21.4667-43.7333q9.3334 0 13.8667-2.6667 4.5333-2.6667 5.8666-7.0667 1.3334-4.5333 1.3334-12 0-7.6-1.2-12-1.0667-4.4-5.2-7.0666-4-2.6667-12.6667-2.6667h-35.7333v43.4667zm63.6625 43.7333v-37.4667l-34.8-54.2666h5.6l31.2 49.2h.9333l31.3334-49.2h5.4666l-34.9333 54.2666v37.4667zm79.7979 1.0666q-17.8666 0-25.0666-3.0666-7.2-3.0667-9.3334-12.1333-2-9.0667-2-31.7334 0-22.6666 2-31.7333 2.1334-9.0666 9.3334-12.1333 7.2-3.0667 25.0666-3.0667 17.8667 0 25.0667 3.0667 7.2 3.0666 9.2 12.1333 2.1333 9.0667 2.1333 31.7333 0 22.6667-2.1333 31.7334-2 9.0666-9.2 12.1333-7.2 3.0667-25.0667 3.0667zm0-4.5333q16.1334 0 21.8667-2 5.8667-2.1333 7.7333-10.2666 2-8.2667 2-30.1334 0-21.8666-2-30-1.8666-8.2666-7.7333-10.2666-5.7333-2.1334-21.8667-2.1334-16.1333 0-22 2.1334-5.7333 2-7.7333 10.2666-1.8667 8.1334-1.8667 30 0 21.8667 1.8667 30.1334 2 8.1333 7.7333 10.2666 5.8667 2 22 2zm153.3563 3.4667l-27.0667-83.0667h-.5333l-27.0667 83.0667h-4.6666l-27.2-91.7333h5.2l24.1333 83.0666h.8l26.6667-83.0666h5.0666l26.6667 83.0666h.8l24.1333-83.0666h4.9333l-27.2 91.7333zm104.6416 0l-12.1333-29.6h-45.8667l-12.1333 29.6h-5.0667l37.8667-91.7333h4.6667l37.8666 91.7333zm-34.6666-84.9333h-.6667l-20.8 50.8h42.1333zm51.6125 84.9333v-91.7333h4.8v87.2h46.6666v4.5333zm62.3541 0v-91.7333h4.8v87.2h46.6667v4.5333z"/>
|
||||
<path fill="url(#f)" d="M712.7803 287.4374v-91.7333h56.8v4.5333h-52v38.5333h45.7333v4.5334h-45.7333v39.6h52v4.5333z" letter-spacing="-2"/>
|
||||
<path fill="url(#g)" d="M811.5345 287.4374v-87.2h-31.0667v-4.5333h66.9333v4.5333h-31.0666v87.2z" font-size="133.3333"/>
|
||||
</g>
|
||||
<path fill="url(#i)" d="M359.2174 367.2394l-7.28-17.76h-27.52l-7.28 17.76h-3.04l22.72-55.04h2.8l22.72 55.04zm-20.8-50.96h-.4l-12.48 30.48h25.28zm41.0388 50.96v-52.32h-18.64v-2.72h40.16v2.72h-18.64v52.32zm53.7625.64q-10.72 0-15.04-1.84-4.32-1.84-5.6-7.28-1.2-5.44-1.2-19.04 0-13.6 1.2-19.04 1.28-5.44 5.6-7.28 4.32-1.84 15.04-1.84 10.72 0 15.04 1.84 4.32 1.84 5.52 7.28 1.28 5.44 1.28 19.04 0 13.6-1.28 19.04-1.2 5.44-5.52 7.28-4.32 1.84-15.04 1.84zm0-2.72q9.68 0 13.12-1.2 3.52-1.28 4.64-6.16 1.2-4.96 1.2-18.08 0-13.12-1.2-18-1.12-4.96-4.64-6.16-3.44-1.28-13.12-1.28t-13.2 1.28q-3.44 1.2-4.64 6.16-1.12 4.88-1.12 18 0 13.12 1.12 18.08 1.2 4.88 4.64 6.16 3.52 1.2 13.2 1.2zm90.205 2.08v-49.44h-.32l-22.96 49.44h-2.56l-22.96-49.44h-.32v49.44h-2.8v-55.04h3.68l23.52 50.8h.4l23.6-50.8h3.6v55.04z" transform="translate(9.5 -183.1415)"/>
|
||||
</svg>
|
||||
<img src="statics/loki.svg" width="400" class="q-mb-md">
|
||||
|
||||
<div class="q-mt-xl q-mb-lg">
|
||||
<q-spinner color="primary" :size="30" />
|
||||
|
|
|
@ -1,113 +1,52 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<q-page class="welcome">
|
||||
|
||||
<q-stepper class="no-shadow" ref="stepper" :color="theme == 'dark' ? 'light' : 'dark'" dark>
|
||||
<q-stepper class="no-shadow" ref="stepper" :color="theme == 'dark' ? 'light' : 'dark'" dark @step="onStep">
|
||||
|
||||
<q-step default title="Welcome">
|
||||
<q-step default title="Welcome" class="first-step">
|
||||
|
||||
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 859.4011 116.8585" height="64">
|
||||
<defs>
|
||||
<linearGradient id="a" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0" stop-color="#62c9f3"/>
|
||||
<stop offset="1" stop-color="#3d58b0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="c" x2="0" y1="168.1192" y2="286.5673" gradientTransform="translate(82.8992 -169.0231)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="e" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="f" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
<linearGradient id="g" x1="138.7479" x2="138.7479" y1="10.1293" y2="104.051" gradientTransform="translate(0 183.1415)" gradientUnits="userSpaceOnUse" xlink:href="#a"/>
|
||||
</defs>
|
||||
<path fill="url(#c)" d="M41.7535 41.89H75.105v33.0785H41.7535zm-4.2333-4.2334v41.5453h41.8181V37.6565zM58.4295 4.2333c29.9566 0 54.1957 24.2396 54.1957 54.1962 0 29.9566-24.239 54.1957-54.1957 54.1957-29.9566 0-54.1962-24.239-54.1962-54.1957 0-29.9566 24.2396-54.1962 54.1962-54.1962zm0-4.2333C26.185 0 0 26.185 0 58.4295s26.185 58.429 58.4295 58.429 58.429-26.1845 58.429-58.429S90.674 0 58.4295 0z"/>
|
||||
<g transform="translate(12 -183.1415)">
|
||||
<path fill="url(#e)" d="M191.1345 287.4374l-15.7334-39.4667q-1.7333.2667-5.4666.2667h-34v39.2h-4.8v-91.7333h40.8q10.5333 0 15.7333 3.6 5.2 3.6 6.5333 9.0666 1.3334 5.3334 1.3334 13.6 0 6.6667-.9334 11.4667-.9333 4.6667-4.2666 8.4-3.3334 3.7333-10 5.3333l16.1333 40.2667zm-21.4667-43.7333q9.3334 0 13.8667-2.6667 4.5333-2.6667 5.8666-7.0667 1.3334-4.5333 1.3334-12 0-7.6-1.2-12-1.0667-4.4-5.2-7.0666-4-2.6667-12.6667-2.6667h-35.7333v43.4667zm63.6625 43.7333v-37.4667l-34.8-54.2666h5.6l31.2 49.2h.9333l31.3334-49.2h5.4666l-34.9333 54.2666v37.4667zm79.7979 1.0666q-17.8666 0-25.0666-3.0666-7.2-3.0667-9.3334-12.1333-2-9.0667-2-31.7334 0-22.6666 2-31.7333 2.1334-9.0666 9.3334-12.1333 7.2-3.0667 25.0666-3.0667 17.8667 0 25.0667 3.0667 7.2 3.0666 9.2 12.1333 2.1333 9.0667 2.1333 31.7333 0 22.6667-2.1333 31.7334-2 9.0666-9.2 12.1333-7.2 3.0667-25.0667 3.0667zm0-4.5333q16.1334 0 21.8667-2 5.8667-2.1333 7.7333-10.2666 2-8.2667 2-30.1334 0-21.8666-2-30-1.8666-8.2666-7.7333-10.2666-5.7333-2.1334-21.8667-2.1334-16.1333 0-22 2.1334-5.7333 2-7.7333 10.2666-1.8667 8.1334-1.8667 30 0 21.8667 1.8667 30.1334 2 8.1333 7.7333 10.2666 5.8667 2 22 2zm153.3563 3.4667l-27.0667-83.0667h-.5333l-27.0667 83.0667h-4.6666l-27.2-91.7333h5.2l24.1333 83.0666h.8l26.6667-83.0666h5.0666l26.6667 83.0666h.8l24.1333-83.0666h4.9333l-27.2 91.7333zm104.6416 0l-12.1333-29.6h-45.8667l-12.1333 29.6h-5.0667l37.8667-91.7333h4.6667l37.8666 91.7333zm-34.6666-84.9333h-.6667l-20.8 50.8h42.1333zm51.6125 84.9333v-91.7333h4.8v87.2h46.6666v4.5333zm62.3541 0v-91.7333h4.8v87.2h46.6667v4.5333z"/>
|
||||
<path fill="url(#f)" d="M712.7803 287.4374v-91.7333h56.8v4.5333h-52v38.5333h45.7333v4.5334h-45.7333v39.6h52v4.5333z"/>
|
||||
<path fill="url(#g)" d="M811.5345 287.4374v-87.2h-31.0667v-4.5333h66.9333v4.5333h-31.0666v87.2z"/>
|
||||
</g>
|
||||
</svg>
|
||||
<div class="welcome-container">
|
||||
<img src="statics/loki.svg" height="100" class="q-mb-md">
|
||||
<div>Version: ATOM v{{version}}-v{{daemonVersion}}</div>
|
||||
|
||||
<div>Version: ATOM v{{version}}-v{{daemonVersion}}</div>
|
||||
<h6 class="q-mb-md" style="font-weight: 300">Select language:</h6>
|
||||
|
||||
<h6 class="q-mb-md" style="font-weight: 300">Select Appearance:</h6>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="choose_theme"
|
||||
toggle-color="primary"
|
||||
size="md"
|
||||
:options="[
|
||||
{label: 'Light theme', value: 'light', icon: 'brightness_5'},
|
||||
{label: 'Dark theme', value: 'dark', icon: 'brightness_2'},
|
||||
]"
|
||||
<q-btn
|
||||
color="primary"
|
||||
size="md"
|
||||
icon="language"
|
||||
label="English"
|
||||
@click="clickNext()"
|
||||
/>
|
||||
|
||||
|
||||
<h6 class="q-mb-md" style="font-weight: 300">Select language:</h6>
|
||||
|
||||
<q-btn-toggle
|
||||
v-model="choose_lang"
|
||||
toggle-color="primary"
|
||||
size="md"
|
||||
:options="[
|
||||
{label: 'English', value: 'EN', icon: 'language'},
|
||||
]"
|
||||
/>
|
||||
|
||||
<p class="q-mt-md">More languages coming soon</p>
|
||||
<p class="q-mt-md">More languages coming soon</p>
|
||||
</div>
|
||||
|
||||
</q-step>
|
||||
|
||||
<q-step title="Configure">
|
||||
|
||||
<SettingsGeneral ref="settingsGeneral"></SettingsGeneral>
|
||||
|
||||
<SettingsGeneral randomise_remote ref="settingsGeneral" />
|
||||
</q-step>
|
||||
|
||||
<q-step title="Review">
|
||||
|
||||
<h2 class="q-mt-none q-mb-none text-weight-thin">You're almost set!</h2>
|
||||
|
||||
<h6 class="q-mb-md q-mt-md" style="font-weight: 300">Review settings:</h6>
|
||||
|
||||
<p>You are using a
|
||||
<template v-if="pending_config.daemon.type == 'local'">
|
||||
<code>local node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.daemon.type == 'local_remote'">
|
||||
<code>local + remote node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.daemon.type == 'remote'">
|
||||
<code>remote node</code>
|
||||
</template>
|
||||
<template v-if="pending_config.app.testnet">
|
||||
<code>on testnet</code>
|
||||
</template>
|
||||
and will store data in
|
||||
<code>{{ pending_config.app.data_dir }}</code>
|
||||
</p>
|
||||
|
||||
<p>Press next to get started!</p>
|
||||
|
||||
</q-step>
|
||||
|
||||
</q-stepper>
|
||||
|
||||
<q-layout-footer class="no-shadow q-pa-sm">
|
||||
<q-layout-footer v-if="!is_first_page" class="no-shadow q-pa-sm">
|
||||
<div class="row justify-end">
|
||||
<div>
|
||||
<q-btn
|
||||
flat
|
||||
@click="clickPrev()"
|
||||
label="Back"
|
||||
/>
|
||||
<q-btn
|
||||
flat
|
||||
@click="clickPrev()"
|
||||
label="Back"
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
<q-btn
|
||||
class="q-ml-sm"
|
||||
color="primary"
|
||||
@click="clickNext()"
|
||||
label="Next"
|
||||
/>
|
||||
@click="clickNext()"
|
||||
label="Next"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</q-layout-footer>
|
||||
|
||||
</q-page>
|
||||
|
@ -120,27 +59,19 @@ import SettingsGeneral from "components/settings_general"
|
|||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
pending_config: state => state.gateway.app.pending_config
|
||||
pending_config: state => state.gateway.app.pending_config,
|
||||
config_daemon (state) {
|
||||
return this.pending_config.daemons[this.pending_config.app.net_type]
|
||||
},
|
||||
}),
|
||||
data() {
|
||||
return {
|
||||
choose_theme: "light",
|
||||
is_first_page: true,
|
||||
choose_lang: "EN",
|
||||
version: "",
|
||||
daemonVersion: ""
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
choose_theme: function (val) {
|
||||
this.$store.commit("gateway/set_app_data", {
|
||||
config: {
|
||||
appearance: {
|
||||
theme: val
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
||||
this.version = version
|
||||
|
@ -154,6 +85,9 @@ export default {
|
|||
});
|
||||
},
|
||||
methods: {
|
||||
onStep () {
|
||||
this.is_first_page = this.$refs.stepper.steps[0].active
|
||||
},
|
||||
clickNext () {
|
||||
if(this.$refs.stepper.steps[this.$refs.stepper.length-1].active) {
|
||||
this.$gateway.send("core", "save_config_init", this.pending_config);
|
||||
|
@ -174,6 +108,21 @@ export default {
|
|||
|
||||
<style lang="scss">
|
||||
|
||||
.welcome {
|
||||
.welcome-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.first-step .q-stepper-step-inner {
|
||||
min-height: 250px;
|
||||
height: calc(100vh - 102px);
|
||||
}
|
||||
}
|
||||
|
||||
.language-item {
|
||||
|
||||
padding: 10px 30px 10px 20px;
|
||||
|
|
|
@ -1,46 +1,43 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div class="q-mx-md">
|
||||
<q-field class="q-mt-none">
|
||||
<q-page class="create-wallet">
|
||||
<div class="fields q-mx-md q-mt-md">
|
||||
<LokiField label="Wallet name" :error="$v.wallet.name.$error">
|
||||
<q-input
|
||||
v-model="wallet.name"
|
||||
float-label="Wallet name"
|
||||
@blur="$v.wallet.name.$touch"
|
||||
:error="$v.wallet.name.$error"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</q-field>
|
||||
placeholder="A name for your wallet"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<LokiField label="Seed Language">
|
||||
<q-select
|
||||
v-model="wallet.language"
|
||||
float-label="Seed language"
|
||||
:options="languageOptions"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</q-field>
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-md">
|
||||
<div><q-radio v-model="wallet.type" val="long" label="Long address" /></div>
|
||||
<div><q-radio v-model="wallet.type" val="kurz" label="Short (kurz) address" /></div>
|
||||
</div>
|
||||
</q-field>
|
||||
<LokiField label="Password" optional>
|
||||
<q-input
|
||||
v-model="wallet.password"
|
||||
type="password"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="An optional password for the wallet"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<p v-if="wallet.type == 'long'">
|
||||
Create both public/private view & spend keys. Allows creation of view-only wallets.
|
||||
</p>
|
||||
<p v-if="wallet.type == 'kurz'">
|
||||
Create shorter style address with only private view & spend keys. Does NOT support view-only wallets.
|
||||
</p>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password" type="password" float-label="Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password_confirm" type="password" float-label="Confirm Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField label="Confirm Password">
|
||||
<q-input
|
||||
v-model="wallet.password_confirm"
|
||||
type="password"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-btn color="primary" @click="create" label="Create wallet" />
|
||||
|
@ -53,13 +50,13 @@
|
|||
<script>
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import { mapState } from "vuex"
|
||||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
wallet: {
|
||||
name: "",
|
||||
language: "English",
|
||||
type: "long",
|
||||
password: "",
|
||||
password_confirm: ""
|
||||
},
|
||||
|
@ -136,18 +133,48 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
// Warn user if no password is set
|
||||
let passwordPromise = Promise.resolve();
|
||||
if (!this.wallet.password) {
|
||||
passwordPromise = this.$q.dialog({
|
||||
title: "No password set",
|
||||
message: "Are you sure you want to create a wallet with no password?",
|
||||
ok: {
|
||||
label: "YES",
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme === "dark" ? "white" : "dark"
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
this.$gateway.send("wallet", "create_wallet", this.wallet);
|
||||
passwordPromise
|
||||
.then(() => {
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
this.$gateway.send("wallet", "create_wallet", this.wallet)
|
||||
})
|
||||
.catch(() => {})
|
||||
},
|
||||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.create-wallet {
|
||||
.fields {
|
||||
> * {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,32 +1,10 @@
|
|||
<template>
|
||||
<q-page padding>
|
||||
|
||||
<AddressHeader :address="info.address" :title="info.name" />
|
||||
|
||||
<template v-if="secret.mnemonic">
|
||||
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
{{ secret.mnemonic }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('mnemonic', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy seed words
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="secret.view_key != secret.spend_key">
|
||||
<h6 class="q-mb-xs">View key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.view_key }}
|
||||
<q-page padding class="created">
|
||||
<div class="col wallet q-mb-lg">
|
||||
<h6>{{walletName}}</h6>
|
||||
<div class="row items-center">
|
||||
<div class="col address">
|
||||
{{ info.address }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
|
@ -39,30 +17,70 @@
|
|||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
|
||||
<template v-if="!/^0*$/.test(secret.spend_key)">
|
||||
<h6 class="q-mb-xs">Spend key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.spend_key }}
|
||||
<template v-if="secret.mnemonic">
|
||||
<div class="seed-box col">
|
||||
<h6 class="q-mb-xs q-mt-lg">Seed words</h6>
|
||||
<div class="seed q-my-lg">
|
||||
{{ secret.mnemonic }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<div class="q-my-md warning">
|
||||
Please copy and save these in a secure location!
|
||||
</div>
|
||||
<div>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy spend key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
color="primary"
|
||||
size="md"
|
||||
icon="file_copy"
|
||||
label="Copy seed words"
|
||||
@click="copyPrivateKey('mnemonic', $event)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<q-field>
|
||||
<q-btn class="q-mt-lg" color="primary" @click="open" label="Open wallet" />
|
||||
</q-field>
|
||||
<q-collapsible label="Advanced" header-class="q-mt-sm non-selectable row reverse advanced-options-label">
|
||||
<template v-if="secret.view_key != secret.spend_key">
|
||||
<h6 class="q-mb-xs title">View key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.view_key }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('view_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy view key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-if="!/^0*$/.test(secret.spend_key)">
|
||||
<h6 class="q-mb-xs title">Spend key</h6>
|
||||
<div class="row">
|
||||
<div class="col" style="word-break:break-all;">
|
||||
{{ secret.spend_key }}
|
||||
</div>
|
||||
<div class="q-item-side">
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyPrivateKey('spend_key', $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy spend key
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</q-collapsible>
|
||||
|
||||
<q-btn class="q-mt-lg" color="primary" @click="open" label="Open wallet" />
|
||||
|
||||
</q-page>
|
||||
</template>
|
||||
|
@ -75,6 +93,9 @@ export default {
|
|||
computed: mapState({
|
||||
info: state => state.gateway.wallet.info,
|
||||
secret: state => state.gateway.wallet.secret,
|
||||
walletName (state) {
|
||||
return `Wallet: ${this.info.name}`
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
open() {
|
||||
|
@ -138,5 +159,45 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.created {
|
||||
.wallet h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.address {
|
||||
text-align: center;
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.seed-box {
|
||||
border: 1px solid white;
|
||||
border-radius: 3px;
|
||||
margin: 16px;
|
||||
padding: 16px;
|
||||
|
||||
div, h6 {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.seed {
|
||||
font-size: 24px;
|
||||
text-transform: uppercase;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.warning {
|
||||
color: goldenrod;
|
||||
}
|
||||
}
|
||||
h6 {
|
||||
font-size: 18px;
|
||||
margin: 8px 0;
|
||||
font-weight: 450;
|
||||
}
|
||||
.advanced-options-label {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
139
src/pages/wallet-select/import-old-gui.vue
Normal file
|
@ -0,0 +1,139 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div class="q-mx-md import-old-gui">
|
||||
<q-list link dark no-border class="wallet-list">
|
||||
<q-item v-for="state in directory_state" :key="state.directory" :class="{selected : state.selected}">
|
||||
<q-item-side>
|
||||
<q-checkbox v-model="state.selected" />
|
||||
</q-item-side>
|
||||
<q-item-main @click.native="state.selected = !state.selected">
|
||||
<q-item-tile label>{{ state.directory }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-select hide-underline dark class="q-ma-none full-width" v-model="state.type" :options="selectOptions" />
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<q-field>
|
||||
<q-btn color="primary" @click="import_wallets" label="Import wallets" :disable="selectedWallets.length === 0"/>
|
||||
</q-field>
|
||||
</div>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapState } from "vuex"
|
||||
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
directory_state: [],
|
||||
}
|
||||
},
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
directories: state => state.gateway.wallets.directories,
|
||||
old_gui_import_status: state => state.gateway.old_gui_import_status,
|
||||
selectOptions: state => [
|
||||
{
|
||||
label: 'Main',
|
||||
value: 'mainnet'
|
||||
},
|
||||
{
|
||||
label: 'Staging',
|
||||
value: 'stagenet'
|
||||
},
|
||||
{
|
||||
label: 'Test',
|
||||
value: 'testnet'
|
||||
},
|
||||
],
|
||||
selectedWallets () {
|
||||
return this.directory_state.filter(s => s.selected)
|
||||
}
|
||||
}),
|
||||
watch: {
|
||||
directories: {
|
||||
handler(val, old) {
|
||||
this.populate_state()
|
||||
},
|
||||
deep: true
|
||||
},
|
||||
old_gui_import_status: {
|
||||
handler(val, old) {
|
||||
if(val.code == old.code) return
|
||||
|
||||
const { code, failed_wallets } = this.old_gui_import_status
|
||||
|
||||
// Imported
|
||||
if (code === 0) {
|
||||
this.$q.loading.hide()
|
||||
if (failed_wallets.length === 0) {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
} else {
|
||||
failed_wallets.forEach(wallet => {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
timeout: 3000,
|
||||
message: `Failed to import wallet: ${wallet}`
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
created () {
|
||||
this.$gateway.send("wallet", "list_wallets")
|
||||
this.populate_state()
|
||||
},
|
||||
methods: {
|
||||
populate_state () {
|
||||
// Keep any directories that intersect
|
||||
const new_state = this.directory_state.filter(state => this.directories.includes(state.directory))
|
||||
|
||||
// Add in new directories
|
||||
this.directories
|
||||
.filter(dir => !new_state.find(state => state.directory === dir))
|
||||
.forEach(directory => {
|
||||
new_state.push({
|
||||
directory,
|
||||
selected: false,
|
||||
type: "mainnet"
|
||||
})
|
||||
});
|
||||
|
||||
// Sort them
|
||||
this.directory_state = new_state.sort(function(a, b){
|
||||
return a.directory.localeCompare(b.directory);
|
||||
})
|
||||
},
|
||||
import_wallets() {
|
||||
this.$q.loading.show({
|
||||
delay: 0
|
||||
})
|
||||
this.$gateway.send("wallet", "copy_old_gui_wallets", {
|
||||
wallets: this.selectedWallets
|
||||
})
|
||||
},
|
||||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
}
|
||||
},
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.import-old-gui {
|
||||
.wallet-list {
|
||||
.q-item {
|
||||
margin: 10px 0px;
|
||||
margin-bottom: 0px;
|
||||
padding: 14px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -137,7 +137,18 @@ export default {
|
|||
validations: {
|
||||
wallet: {
|
||||
name: { required },
|
||||
address: { required, address },
|
||||
address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
},
|
||||
viewkey: { required, privkey },
|
||||
refresh_start_height: { numeric }
|
||||
}
|
||||
|
|
|
@ -1,36 +1,30 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div class="q-mx-md">
|
||||
<div class="q-mx-md import-wallet">
|
||||
|
||||
<q-field class="q-mt-none">
|
||||
<LokiField label="New wallet name" :error="$v.wallet.name.$error">
|
||||
<q-input
|
||||
v-model="wallet.name"
|
||||
float-label="New wallet name"
|
||||
placeholder="A name for your wallet"
|
||||
@blur="$v.wallet.name.$touch"
|
||||
:error="$v.wallet.name.$error"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</q-field>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<div class="row gutter-sm">
|
||||
<div class="col">
|
||||
<q-input v-model="wallet.path" stack-label="Wallet file" disable :dark="theme=='dark'" />
|
||||
<input type="file" id="walletPath" v-on:change="setWalletPath" ref="fileInput" hidden />
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<q-btn v-on:click="selectFile" class="float-right" :text-color="theme=='dark'?'white':'dark'">Select wallet file</q-btn>
|
||||
</div>
|
||||
</div>
|
||||
</q-field>
|
||||
<LokiField label="Wallet file" disable-hover>
|
||||
<q-input v-model="wallet.path" placeholder="Please select a file" disable :dark="theme=='dark'" hide-underline/>
|
||||
<input type="file" id="walletPath" v-on:change="setWalletPath" ref="fileInput" hidden />
|
||||
<q-btn color="secondary" v-on:click="selectFile" :text-color="theme=='dark'?'white':'dark'">Select wallet file</q-btn>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password" type="password" float-label="Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField label="Password">
|
||||
<q-input v-model="wallet.password" placeholder="An optional password for the wallet" type="password" :dark="theme=='dark'" hide-underline />
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password_confirm" type="password" float-label="Confirm Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField label="Confirm Password">
|
||||
<q-input v-model="wallet.password_confirm" type="password" :dark="theme=='dark'" hide-underline />
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-btn color="primary" @click="import_wallet" label="Import wallet" />
|
||||
|
@ -44,6 +38,7 @@
|
|||
<script>
|
||||
import { required } from "vuelidate/lib/validators"
|
||||
import { mapState } from "vuex"
|
||||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
|
@ -124,9 +119,24 @@ export default {
|
|||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.import-wallet {
|
||||
.q-if-disabled {
|
||||
cursor: default !important;
|
||||
.q-input-target {
|
||||
cursor: default !important;
|
||||
}
|
||||
}
|
||||
|
||||
.loki-field {
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
<template>
|
||||
<q-page>
|
||||
|
||||
<q-list link no-border :dark="theme=='dark'">
|
||||
<q-list class="wallet-list" link no-border :dark="theme=='dark'">
|
||||
<template v-if="wallets.list.length">
|
||||
<q-list-header>Open wallet</q-list-header>
|
||||
<q-item v-for="(wallet, index) in wallets.list" @click.native="openWallet(wallet)">
|
||||
<div class="header row justify-between items-center">
|
||||
<div class="header-title">Your wallets</div>
|
||||
<q-btn class="add" icon="add" size="md" color="primary" v-if="wallets.list.length">
|
||||
<q-popover class="header-popover">
|
||||
<q-list separator link>
|
||||
<q-item v-for="action in actions" @click.native="action.handler" :key="action.name">
|
||||
<q-item-main :label="action.name" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-popover>
|
||||
|
||||
</q-btn>
|
||||
</div>
|
||||
<div class="hr-separator" />
|
||||
<q-item v-for="wallet in wallets.list" @click.native="openWallet(wallet)" :key="`${wallet.address}-${wallet.name}`">
|
||||
<q-item-side>
|
||||
<Identicon :address="wallet.address" :ref="`${index}-identicon`" />
|
||||
<div class="wallet-icon">
|
||||
<svg width="48" viewBox="0 0 17 16" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" class="si-glyph si-glyph-wallet"><title>969</title><defs class="si-glyph-fill"></defs><g stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"><g transform="translate(1.000000, 0.000000)" fill="#434343"><path d="M7.988,10.635 L7.988,8.327 C7.988,7.578 8.561,6.969 9.267,6.969 L13.964,6.969 L13.964,5.531 C13.964,4.849 13.56,4.279 13.007,4.093 L13.007,4.094 L11.356,4.08 L11.336,4.022 L3.925,4.022 L3.784,4.07 L1.17,4.068 L1.165,4.047 C0.529,4.167 0.017,4.743 0.017,5.484 L0.017,13.437 C0.017,14.269 0.665,14.992 1.408,14.992 L12.622,14.992 C13.365,14.992 13.965,14.316 13.965,13.484 L13.965,12.031 L9.268,12.031 C8.562,12.031 7.988,11.384 7.988,10.635 L7.988,10.635 Z" class="si-glyph-fill"></path><path d="M14.996,8.061 L14.947,8.061 L9.989,8.061 C9.46,8.061 9.031,8.529 9.031,9.106 L9.031,9.922 C9.031,10.498 9.46,10.966 9.989,10.966 L14.947,10.966 L14.996,10.966 C15.525,10.966 15.955,10.498 15.955,9.922 L15.955,9.106 C15.955,8.528 15.525,8.061 14.996,8.061 L14.996,8.061 Z M12.031,10.016 L9.969,10.016 L9.969,9 L12.031,9 L12.031,10.016 L12.031,10.016 Z" class="si-glyph-fill"></path><path d="M3.926,4.022 L10.557,1.753 L11.337,4.022 L12.622,4.022 C12.757,4.022 12.885,4.051 13.008,4.092 L11.619,0.051 L1.049,3.572 L1.166,4.048 C1.245,4.033 1.326,4.023 1.408,4.023 L3.926,4.023 L3.926,4.022 Z" class="si-glyph-fill"></path></g></g></svg>
|
||||
</div>
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile label>{{ wallet.name }}</q-item-tile>
|
||||
<q-item-tile class="monospace ellipsis" sublabel>{{ wallet.address }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyAddress(wallet.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
|
@ -34,32 +38,15 @@
|
|||
@click.native="copyAddress(wallet.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs[`${index}-identicon`][0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
</q-item>
|
||||
<q-item-separator />
|
||||
</template>
|
||||
<q-item @click.native="createNewWallet()">
|
||||
<q-item-main label="Create new wallet" />
|
||||
</q-item>
|
||||
<q-item @click.native="restoreWallet()">
|
||||
<q-item-main label="Restore wallet from seed" />
|
||||
</q-item>
|
||||
<q-item @click.native="restoreViewWallet()">
|
||||
<q-item-main label="Restore view-only wallet" />
|
||||
</q-item>
|
||||
<q-item @click.native="importWallet()">
|
||||
<q-item-main label="Import wallet from file" />
|
||||
</q-item>
|
||||
<template v-if="wallets.legacy.length">
|
||||
<q-item @click.native="importLegacyWallet()">
|
||||
<q-item-main label="Import wallet from legacy gui" />
|
||||
<template v-else>
|
||||
<q-item v-for="action in actions" @click.native="action.handler" :key="action.name">
|
||||
<q-item-main :label="action.name" />
|
||||
</q-item>
|
||||
</template>
|
||||
</q-list>
|
||||
|
@ -71,12 +58,45 @@
|
|||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import Identicon from "components/identicon"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
wallets: state => state.gateway.wallets,
|
||||
status: state => state.gateway.wallet.status
|
||||
status: state => state.gateway.wallet.status,
|
||||
actions (status) {
|
||||
// TODO: Add this in once LOKI has the functionality
|
||||
// <q-item @click.native="restoreViewWallet()">
|
||||
// <q-item-main label="Restore view-only wallet" />
|
||||
// </q-item>
|
||||
const actions = [
|
||||
{
|
||||
name: "Create new wallet",
|
||||
handler: this.createNewWallet,
|
||||
},
|
||||
{
|
||||
name: "Restore wallet from seed",
|
||||
handler: this.restoreWallet,
|
||||
},
|
||||
{
|
||||
name: "Import wallet from file",
|
||||
handler: this.importWallet,
|
||||
}
|
||||
];
|
||||
|
||||
if (this.wallets.directories.length > 0) {
|
||||
actions.push( {
|
||||
name: "Import wallets from old GUI",
|
||||
handler: this.importOldGuiWallets,
|
||||
})
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
}),
|
||||
created () {
|
||||
this.$gateway.send("wallet", "list_wallets")
|
||||
},
|
||||
methods: {
|
||||
openWallet(wallet) {
|
||||
if(wallet.password_protected !== false) {
|
||||
|
@ -122,6 +142,9 @@ export default {
|
|||
importWallet() {
|
||||
this.$router.replace({ path: "wallet-select/import" });
|
||||
},
|
||||
importOldGuiWallets() {
|
||||
this.$router.replace({ path: "wallet-select/import-old-gui" });
|
||||
},
|
||||
importLegacyWallet() {
|
||||
this.$router.replace({ path: "wallet-select/import-legacy" });
|
||||
},
|
||||
|
@ -175,5 +198,32 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.header-popover.q-popover {
|
||||
max-width: 250px !important;
|
||||
}
|
||||
|
||||
.wallet-list {
|
||||
.header {
|
||||
margin: 0 16px;
|
||||
margin-bottom: 8px;
|
||||
min-height: 36px;
|
||||
|
||||
.header-title {
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.add {
|
||||
width: 38px;
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
.q-item {
|
||||
margin: 10px 16px;
|
||||
margin-bottom: 0px;
|
||||
padding: 14px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,74 +1,83 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<div class="q-mx-md">
|
||||
<q-field class="q-mt-none">
|
||||
<LokiField class="q-mt-md" label="Wallet name" :error="$v.wallet.name.$error">
|
||||
<q-input
|
||||
v-model="wallet.name"
|
||||
float-label="Wallet name"
|
||||
placeholder="A name for your wallet"
|
||||
@blur="$v.wallet.name.$touch"
|
||||
:error="$v.wallet.name.$error"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</q-field>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<LokiField class="q-mt-md" label="Mnemonic seed" :error="$v.wallet.seed.$error">
|
||||
<q-input
|
||||
v-model="wallet.seed"
|
||||
float-label="Mnemonic seed"
|
||||
placeholder="25 (or 24) word mnemonic seed"
|
||||
type="textarea"
|
||||
@blur="$v.wallet.seed.$touch"
|
||||
:error="$v.wallet.seed.$error"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</q-field>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<div class="row items-center gutter-sm">
|
||||
<div class="col">
|
||||
<template v-if="wallet.refresh_type=='date'">
|
||||
<q-datetime v-model="wallet.refresh_start_date" type="date"
|
||||
float-label="Restore from date"
|
||||
modal :min="1492486495000" :max="Date.now()"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</template>
|
||||
<template v-else-if="wallet.refresh_type=='height'">
|
||||
<q-input v-model="wallet.refresh_start_height" type="number"
|
||||
min="0" float-label="Restore from block height"
|
||||
@blur="$v.wallet.refresh_start_height.$touch"
|
||||
:error="$v.wallet.refresh_start_height.$error"
|
||||
:dark="theme=='dark'"
|
||||
/>
|
||||
</template>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<template v-if="wallet.refresh_type=='date'">
|
||||
<q-btn @click="wallet.refresh_type='height'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
|
||||
<div style="width: 80px;" class="text-center">
|
||||
<q-icon class="block" name="clear_all" />
|
||||
<div style="font-size:10px">Switch to<br/>height select</div>
|
||||
</div>
|
||||
</q-btn>
|
||||
</template>
|
||||
<template v-else-if="wallet.refresh_type=='height'">
|
||||
<q-btn @click="wallet.refresh_type='date'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
|
||||
<div style="width: 80px;" class="text-center">
|
||||
<q-icon class="block" name="today" />
|
||||
<div style="font-size:10px">Switch to<br/>date select</div>
|
||||
</div>
|
||||
</q-btn>
|
||||
</template>
|
||||
</div>
|
||||
<div class="row items-end q-mt-md">
|
||||
<div class="col">
|
||||
<LokiField v-if="wallet.refresh_type=='date'" label="Restore from date">
|
||||
<q-datetime v-model="wallet.refresh_start_date" type="date"
|
||||
modal :min="1492486495000" :max="Date.now()"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField v-else-if="wallet.refresh_type=='height'" label="Restore from block height" :error="$v.wallet.refresh_start_height.$error">
|
||||
<q-input v-model="wallet.refresh_start_height" type="number"
|
||||
min="0"
|
||||
@blur="$v.wallet.refresh_start_height.$touch"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
</q-field>
|
||||
<div class="col-auto q-ml-sm">
|
||||
<template v-if="wallet.refresh_type=='date'">
|
||||
<q-btn @click="wallet.refresh_type='height'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
|
||||
<div style="width: 80px;" class="text-center">
|
||||
<q-icon class="block" name="clear_all" />
|
||||
<div style="font-size:10px">Switch to<br/>height select</div>
|
||||
</div>
|
||||
</q-btn>
|
||||
</template>
|
||||
<template v-else-if="wallet.refresh_type=='height'">
|
||||
<q-btn @click="wallet.refresh_type='date'" class="float-right" :text-color="theme=='dark'?'white':'dark'" flat>
|
||||
<div style="width: 80px;" class="text-center">
|
||||
<q-icon class="block" name="today" />
|
||||
<div style="font-size:10px">Switch to<br/>date select</div>
|
||||
</div>
|
||||
</q-btn>
|
||||
</template>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password" type="password" float-label="Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField class="q-mt-md" label="Password">
|
||||
<q-input
|
||||
v-model="wallet.password"
|
||||
placeholder="An optional password for the wallet"
|
||||
type="password"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-input v-model="wallet.password_confirm" type="password" float-label="Confirm Password" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField class="q-mt-md" label="Confirm Password">
|
||||
<q-input
|
||||
v-model="wallet.password_confirm"
|
||||
type="password"
|
||||
:dark="theme=='dark'"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
|
||||
<q-field>
|
||||
<q-btn color="primary" @click="restore_wallet" label="Restore wallet" />
|
||||
|
@ -81,6 +90,7 @@
|
|||
<script>
|
||||
import { required, numeric } from "vuelidate/lib/validators"
|
||||
import { mapState } from "vuex"
|
||||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
|
@ -89,7 +99,7 @@ export default {
|
|||
seed: "",
|
||||
refresh_type: "date",
|
||||
refresh_start_height: 0,
|
||||
refresh_start_date: 1492486495000, // timestamp of block 1
|
||||
refresh_start_date: 1525305600000, // timestamp of block 1
|
||||
password: "",
|
||||
password_confirm: ""
|
||||
},
|
||||
|
@ -151,7 +161,11 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
let seed = this.wallet.seed.trim().replace(/\s{2,}/g, " ").split(" ")
|
||||
let seed = this.wallet.seed.trim()
|
||||
.replace(/\n/g, " ")
|
||||
.replace(/\t/g, " ")
|
||||
.replace(/\s{2,}/g, " ")
|
||||
.split(" ")
|
||||
if(seed.length !== 14 && seed.length !== 24 && seed.length !== 25 && seed.length !== 26) {
|
||||
this.$q.notify({
|
||||
type: "negative",
|
||||
|
@ -187,6 +201,9 @@ export default {
|
|||
cancel() {
|
||||
this.$router.replace({ path: "/wallet-select" });
|
||||
}
|
||||
},
|
||||
components: {
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,27 +1,16 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<q-page class="address-book">
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="person" size="24px" /> Address book
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
</div>
|
||||
|
||||
Address book
|
||||
</div>
|
||||
|
||||
<template v-if="address_book_starred.length || address_book.length">
|
||||
<q-list link no-border :dark="theme=='dark'">
|
||||
|
||||
<q-item v-for="(entry, index) in address_book_starred" @click.native="details(entry)">
|
||||
<q-item-side>
|
||||
<Identicon :address="entry.address" :ref="`${index}-starredIdenticon`" />
|
||||
</q-item-side>
|
||||
<template v-if="address_book_combined.length">
|
||||
<q-list link no-border :dark="theme=='dark'" class="loki-list">
|
||||
<q-item class="loki-list-item" v-for="(entry, index) in address_book_combined" @click.native="details(entry)" :key="entry.address">
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ entry.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>{{ entry.name }}</q-item-tile>
|
||||
<q-item-tile class="ellipsis" label>{{ entry.address }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
|
@ -33,7 +22,7 @@
|
|||
Send coins
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-icon size="24px" name="star" />
|
||||
<q-icon size="24px" :name="entry.starred ? 'star' : 'star_border'" />
|
||||
</q-item-side>
|
||||
|
||||
<q-context-menu>
|
||||
|
@ -52,57 +41,6 @@
|
|||
@click.native="copyAddress(entry, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs[`${index}-starredIdenticon`][0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
</q-item>
|
||||
<q-item v-for="(entry, index) in address_book" @click.native="details(entry)">
|
||||
<q-item-side>
|
||||
<Identicon :address="entry.address" :ref="`${index}-normalIdenticon`" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ entry.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>{{ entry.name }}</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px; margin-right: 10px;"
|
||||
size="sm" icon="call_made"
|
||||
:disabled="view_only"
|
||||
@click="sendToAddress(entry, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Send coins
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-icon size="24px" name="star_border" />
|
||||
</q-item-side>
|
||||
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay
|
||||
@click.native="details(entry)">
|
||||
<q-item-main label="Show details" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="sendToAddress(entry, $event)">
|
||||
<q-item-main label="Send to this address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="copyAddress(entry, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs[`${index}-normalIdenticon`][0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
|
@ -140,6 +78,13 @@ export default {
|
|||
address_book_starred: state => state.gateway.wallet.address_list.address_book_starred,
|
||||
is_ready (state) {
|
||||
return this.$store.getters["gateway/isReady"]
|
||||
},
|
||||
address_book_combined (state) {
|
||||
const starred = this.address_book_starred.map(a => ({ ...a, starred: true }));
|
||||
return [
|
||||
...starred,
|
||||
...this.address_book
|
||||
]
|
||||
}
|
||||
}),
|
||||
methods: {
|
||||
|
@ -209,5 +154,15 @@ export default {
|
|||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.address-book {
|
||||
.q-item-label {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.q-item-sublabel, .q-list-header {
|
||||
font-size: 14px;
|
||||
margin-top: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,39 +1,55 @@
|
|||
<template>
|
||||
<q-page>
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_received" size="24px" /> Receive Ryo
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<q-list link no-border :dark="theme=='dark'">
|
||||
<q-page class="receive">
|
||||
<q-list link no-border :dark="theme=='dark'" class="loki-list">
|
||||
|
||||
<q-list-header>My primary address</q-list-header>
|
||||
<q-item v-for="(address, index) in address_list.primary" @click.native="details(address)">
|
||||
<q-item-side>
|
||||
<Identicon :address="address.address" ref="primaryIdenticon" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Primary address</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
|
||||
<q-list class="loki-list-item primary-address" no-border v-for="address in address_list.primary" :key="address.address" @click.native="details(address)">
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Primary address</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)"
|
||||
>
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
<q-item-separator />
|
||||
<q-item class="info">
|
||||
<q-item-main class="flex justify-between">
|
||||
<div class="column">
|
||||
<span>Balance</span>
|
||||
<span class="value">{{address.balance | currency}}</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span>Unlocked balance</span>
|
||||
<span class="value">{{ address.unlocked_balance | currency }}</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span>Unspent outputs</span>
|
||||
<span class="value">{{ address.num_unspent_outputs | toString }}</span>
|
||||
</div>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay
|
||||
|
@ -45,36 +61,58 @@
|
|||
@click.native="copyAddress(address.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs.primaryIdenticon[0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
</q-item>
|
||||
</q-list>
|
||||
|
||||
<template v-if="address_list.used.length">
|
||||
<q-list-header>My used addresses</q-list-header>
|
||||
<q-item v-for="(address, index) in address_list.used" @click.native="details(address)">
|
||||
<q-item-side>
|
||||
<Identicon :address="address.address" :ref="`${index}-usedIdenticon`" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
<q-list class="loki-list-item" no-border v-for="address in address_list.used" @click.native="details(address)" :key="address.address">
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code-grey.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
<q-item-separator />
|
||||
<q-item class="info">
|
||||
<q-item-main class="flex justify-between">
|
||||
<div class="column">
|
||||
<span>Balance</span>
|
||||
<span class="value">{{ address.balance | currency }}</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span>Unlocked balance</span>
|
||||
<span class="value">{{ address.unlocked_balance | currency }}</span>
|
||||
</div>
|
||||
<div class="column">
|
||||
<span>Unspent outputs</span>
|
||||
<span class="value">{{ address.num_unspent_outputs | toString }}</span>
|
||||
</div>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
|
@ -87,38 +125,43 @@
|
|||
@click.native="copyAddress(address.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs[`${index}-usedIdenticon`][0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
</q-item>
|
||||
</q-list>
|
||||
</template>
|
||||
|
||||
|
||||
<template v-if="address_list.unused.length">
|
||||
<q-list-header>My unused addresses</q-list-header>
|
||||
<q-item v-for="(address, index) in address_list.unused" @click.native="details(address)">
|
||||
<q-item-side>
|
||||
<Identicon :address="address.address" :ref="`${index}-unusedIdenticon`" />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-item-tile class="monospace ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
color="primary" style="width:25px;"
|
||||
size="sm" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="center left" self="center right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
<q-list class="loki-list-item" no-border v-for="address in address_list.unused" @click.native="details(address)" :key="address.address">
|
||||
<q-item>
|
||||
<q-item-main>
|
||||
<q-item-tile class="ellipsis" label>{{ address.address }}</q-item-tile>
|
||||
<q-item-tile sublabel>Sub-address (Index {{ address.address_index }})</q-item-tile>
|
||||
</q-item-main>
|
||||
<q-item-side>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md"
|
||||
@click="showQR(address.address, $event)"
|
||||
>
|
||||
<img src="statics/qr-code-grey.svg" height="20" />
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[0, 5]">
|
||||
Show QR code
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
<q-btn
|
||||
flat
|
||||
style="width:25px;"
|
||||
size="md" icon="file_copy"
|
||||
@click="copyAddress(address.address, $event)">
|
||||
<q-tooltip anchor="bottom right" self="top right" :offset="[5, 10]">
|
||||
Copy address
|
||||
</q-tooltip>
|
||||
</q-btn>
|
||||
</q-item-side>
|
||||
</q-item>
|
||||
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
|
@ -131,40 +174,101 @@
|
|||
@click.native="copyAddress(address.address, $event)">
|
||||
<q-item-main label="Copy address" />
|
||||
</q-item>
|
||||
|
||||
<q-item v-close-overlay
|
||||
@click.native="$refs[`${index}-unusedIdenticon`][0].saveIdenticon()">
|
||||
<q-item-main label="Save identicon to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
|
||||
</q-item>
|
||||
</q-list>
|
||||
</template>
|
||||
|
||||
</q-list>
|
||||
<AddressDetails ref="addressDetails" />
|
||||
|
||||
<!-- QR Code -->
|
||||
<template v-if="QR.address != null">
|
||||
<q-modal v-model="QR.visible" minimized :content-css="{padding: '25px'}">
|
||||
|
||||
<div class="text-center q-mb-sm q-pa-md" style="background: white;">
|
||||
<qrcode-vue :value="QR.address" size="240" ref="qr">
|
||||
</qrcode-vue>
|
||||
<q-context-menu>
|
||||
<q-list link separator style="min-width: 150px; max-height: 300px;">
|
||||
<q-item v-close-overlay @click.native="copyQR()">
|
||||
<q-item-main label="Copy QR code" />
|
||||
</q-item>
|
||||
<q-item v-close-overlay @click.native="saveQR()">
|
||||
<q-item-main label="Save QR code to file" />
|
||||
</q-item>
|
||||
</q-list>
|
||||
</q-context-menu>
|
||||
</div>
|
||||
|
||||
<q-btn
|
||||
color="primary"
|
||||
@click="QR.visible = false"
|
||||
label="Close"
|
||||
/>
|
||||
</q-modal>
|
||||
</template>
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
||||
<script>
|
||||
const { clipboard } = require("electron")
|
||||
|
||||
const { clipboard, nativeImage } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import QrcodeVue from "qrcode.vue";
|
||||
import Identicon from "components/identicon"
|
||||
import AddressDetails from "components/address_details"
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
address_list: state => state.gateway.wallet.address_list
|
||||
}),
|
||||
filters: {
|
||||
toString: function (value) {
|
||||
if (typeof value !== "number") return "N/A";
|
||||
return String(value);
|
||||
},
|
||||
currency: function (value) {
|
||||
if (typeof value !== "number") return "N/A";
|
||||
|
||||
const amount = value / 1e9
|
||||
return amount.toLocaleString()
|
||||
}
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
QR: {
|
||||
visible: false,
|
||||
address: null,
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
details (address) {
|
||||
this.$refs.addressDetails.address = address;
|
||||
this.$refs.addressDetails.isVisible = true;
|
||||
},
|
||||
showQR (address, event) {
|
||||
event.stopPropagation()
|
||||
this.QR.visible = true
|
||||
this.QR.address = address
|
||||
},
|
||||
copyQR () {
|
||||
const data = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
const img = nativeImage.createFromDataURL(data)
|
||||
clipboard.writeImage(img)
|
||||
this.$q.notify({
|
||||
type: "positive",
|
||||
timeout: 1000,
|
||||
message: "Copied QR code to clipboard"
|
||||
})
|
||||
},
|
||||
saveQR () {
|
||||
let img = this.$refs.qr.$el.childNodes[0].toDataURL()
|
||||
this.$gateway.send("core", "save_png", {img, type: "QR Code"})
|
||||
},
|
||||
copyAddress (address, event) {
|
||||
event.stopPropagation()
|
||||
for(let i = 0; i < event.path.length; i++) {
|
||||
|
@ -184,6 +288,45 @@ export default {
|
|||
components: {
|
||||
Identicon,
|
||||
AddressDetails,
|
||||
QrcodeVue
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.receive {
|
||||
.q-item-label {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.q-item-sublabel, .q-list-header {
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.loki-list-item {
|
||||
cursor: pointer;
|
||||
|
||||
.q-item {
|
||||
padding-top: 4px;
|
||||
padding-bottom: 4px;
|
||||
}
|
||||
|
||||
.q-item-side {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.info {
|
||||
span {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.value {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -1,118 +1,109 @@
|
|||
<template>
|
||||
<q-page>
|
||||
<q-page class="send">
|
||||
<template v-if="view_only">
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_made" size="24px" /> Send Ryo
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="q-pa-md">
|
||||
|
||||
View-only mode. Please load full wallet in order to send coins.
|
||||
|
||||
</div>
|
||||
|
||||
</template>
|
||||
<template v-else>
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-none items-center non-selectable" style="height: 44px;">
|
||||
|
||||
<div class="col-8">
|
||||
<q-icon name="call_made" size="24px" /> Send Ryo
|
||||
</div>
|
||||
|
||||
<div class="col-4">
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="q-pa-md">
|
||||
|
||||
|
||||
<div class="row items-end gutter-md">
|
||||
|
||||
<div class="col">
|
||||
<q-field class="q-ma-none">
|
||||
<q-input v-model="newTx.amount" float-label="Amount" :dark="theme=='dark'"
|
||||
type="number" min="0" :max="unlocked_balance / 1e9" />
|
||||
</q-field>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<q-btn @click="newTx.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All coins</q-btn>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<q-item class="q-pa-none">
|
||||
<q-item-side>
|
||||
<Identicon :address="newTx.address" menu />
|
||||
</q-item-side>
|
||||
<q-item-main>
|
||||
<q-field>
|
||||
<q-input v-model="newTx.address" float-label="Address"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.newTx.address.$touch"
|
||||
:error="$v.newTx.address.$error"
|
||||
/>
|
||||
</q-field>
|
||||
</q-item-main>
|
||||
</q-item>
|
||||
|
||||
<q-field style="margin-top:0">
|
||||
<q-input v-model="newTx.payment_id" float-label="Payment ID (optional)"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.newTx.payment_id.$touch"
|
||||
:error="$v.newTx.payment_id.$error"
|
||||
/>
|
||||
</q-field>
|
||||
|
||||
<div class="row gutter-md">
|
||||
|
||||
<!-- Amount -->
|
||||
<div class="col-6">
|
||||
<q-field>
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.mixin"
|
||||
float-label="Mixin"
|
||||
:options="mixinOptions"
|
||||
/>
|
||||
</q-field>
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<q-field>
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.priority"
|
||||
float-label="Priority"
|
||||
:options="priorityOptions"
|
||||
/>
|
||||
</q-field>
|
||||
<LokiField label="Amount" :error="$v.newTx.amount.$error">
|
||||
<q-input v-model="newTx.amount"
|
||||
:dark="theme=='dark'"
|
||||
type="number"
|
||||
min="0"
|
||||
:max="unlocked_balance / 1e9"
|
||||
placeholder="0"
|
||||
@blur="$v.newTx.amount.$touch"
|
||||
hide-underline
|
||||
/>
|
||||
<q-btn color="secondary" @click="newTx.amount = unlocked_balance / 1e9" :text-color="theme=='dark'?'white':'dark'">All</q-btn>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Priority -->
|
||||
<div class="col-6">
|
||||
<LokiField label="Priority">
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="newTx.priority"
|
||||
:options="priorityOptions"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Address -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField label="Address" :error="$v.newTx.address.$error">
|
||||
<q-input v-model="newTx.address"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.newTx.address.$touch"
|
||||
:placeholder="address_placeholder"
|
||||
hide-underline
|
||||
/>
|
||||
<q-btn color="secondary" :text-color="theme=='dark'?'white':'dark'" to="addressbook">Contacts</q-btn>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Payment ID -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField label="Payment id" :error="$v.newTx.payment_id.$error" optional>
|
||||
<q-input v-model="newTx.payment_id"
|
||||
:dark="theme=='dark'"
|
||||
@blur="$v.newTx.payment_id.$touch"
|
||||
placeholder="16 or 64 hexadecimal characters"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Notes -->
|
||||
<div class="col q-mt-sm">
|
||||
<LokiField label="Notes" optional>
|
||||
<q-input v-model="newTx.note"
|
||||
type="textarea"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="Additional notes to attach to the transaction"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<!-- Save to address book -->
|
||||
<q-field>
|
||||
<q-checkbox v-model="newTx.address_book.save" label="Save to address book" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
|
||||
<div v-if="newTx.address_book.save">
|
||||
<q-field>
|
||||
<q-input v-model="newTx.address_book.name" float-label="Name" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<q-field>
|
||||
<q-input v-model="newTx.address_book.description" type="textarea" rows="2" float-label="Notes" :dark="theme=='dark'" />
|
||||
</q-field>
|
||||
<LokiField label="Name" optional>
|
||||
<q-input v-model="newTx.address_book.name"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="Name that belongs to this address"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
<LokiField class="q-mt-sm" label="Notes" optional>
|
||||
<q-input v-model="newTx.address_book.description"
|
||||
type="textarea"
|
||||
rows="2"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="Additional notes"
|
||||
hide-underline
|
||||
/>
|
||||
</LokiField>
|
||||
</div>
|
||||
|
||||
<q-field class="q-pt-sm">
|
||||
<q-btn
|
||||
class="send-btn"
|
||||
:disable="!is_able_to_send"
|
||||
color="primary" @click="send()" label="Send" />
|
||||
</q-field>
|
||||
|
@ -131,9 +122,12 @@
|
|||
<script>
|
||||
import { mapState } from "vuex"
|
||||
import { required, decimal } from "vuelidate/lib/validators"
|
||||
import { payment_id, address } from "src/validators/common"
|
||||
import { payment_id, address, greater_than_zero } from "src/validators/common"
|
||||
import Identicon from "components/identicon"
|
||||
import LokiField from "components/loki_field"
|
||||
import WalletPassword from "src/mixins/wallet_password"
|
||||
const objectAssignDeep = require("object-assign-deep");
|
||||
|
||||
export default {
|
||||
computed: mapState({
|
||||
theme: state => state.gateway.app.config.appearance.theme,
|
||||
|
@ -145,6 +139,11 @@ export default {
|
|||
},
|
||||
is_able_to_send (state) {
|
||||
return this.$store.getters["gateway/isAbleToSend"]
|
||||
},
|
||||
address_placeholder (state) {
|
||||
const wallet = state.gateway.wallet.info;
|
||||
const prefix = (wallet && wallet.address && wallet.address[0]) || "L";
|
||||
return `${prefix}..`;
|
||||
}
|
||||
}),
|
||||
data () {
|
||||
|
@ -154,7 +153,6 @@ export default {
|
|||
amount: 0,
|
||||
address: "",
|
||||
payment_id: "",
|
||||
mixin: 12,
|
||||
priority: 0,
|
||||
address_book: {
|
||||
save: false,
|
||||
|
@ -162,17 +160,12 @@ export default {
|
|||
description: ""
|
||||
}
|
||||
},
|
||||
mixinOptions: [
|
||||
{label: "12 mixins (default)", value: 12},
|
||||
{label: "48 mixins (top secret)", value: 48},
|
||||
{label: "96 mixins (paranoid)", value: 60},
|
||||
],
|
||||
priorityOptions: [
|
||||
{label: "Normal (x1 fee)", value: 0},
|
||||
{label: "High (x2 fee)", value: 1},
|
||||
{label: "High (x4 fee)", value: 2},
|
||||
{label: "High (x20 fee)", value: 3},
|
||||
{label: "Highest (x144 fee)", value: 4},
|
||||
{label: "Automatic", value: 0},
|
||||
{label: "Slow", value: 1},
|
||||
{label: "Normal", value: 2},
|
||||
{label: "Fast", value: 3},
|
||||
{label: "Fastest", value: 4},
|
||||
],
|
||||
}
|
||||
},
|
||||
|
@ -180,9 +173,21 @@ export default {
|
|||
newTx: {
|
||||
amount: {
|
||||
required,
|
||||
decimal
|
||||
decimal,
|
||||
greater_than_zero
|
||||
},
|
||||
address: {
|
||||
required,
|
||||
isAddress(value) {
|
||||
if (value === '') return true
|
||||
|
||||
return new Promise(resolve => {
|
||||
address(value, this.$gateway)
|
||||
.then(() => resolve(true))
|
||||
.catch(e => resolve(false))
|
||||
});
|
||||
}
|
||||
},
|
||||
address: { required, address },
|
||||
payment_id: { payment_id }
|
||||
}
|
||||
},
|
||||
|
@ -202,13 +207,13 @@ export default {
|
|||
amount: 0,
|
||||
address: "",
|
||||
payment_id: "",
|
||||
mixin: 12,
|
||||
priority: 0,
|
||||
address_book: {
|
||||
save: false,
|
||||
name: "",
|
||||
description: ""
|
||||
}
|
||||
},
|
||||
note: ""
|
||||
}
|
||||
break;
|
||||
case -1:
|
||||
|
@ -241,7 +246,6 @@ export default {
|
|||
},
|
||||
|
||||
send: function () {
|
||||
|
||||
this.$v.newTx.$touch()
|
||||
|
||||
if(this.newTx.amount < 0) {
|
||||
|
@ -293,38 +297,36 @@ export default {
|
|||
return
|
||||
}
|
||||
|
||||
this.$q.dialog({
|
||||
this.showPasswordConfirmation({
|
||||
title: "Transfer",
|
||||
message: "Enter wallet password to continue.",
|
||||
prompt: {
|
||||
model: "",
|
||||
type: "password"
|
||||
},
|
||||
noPasswordMessage: "Do you want to send the transaction?",
|
||||
ok: {
|
||||
label: "SEND"
|
||||
},
|
||||
cancel: {
|
||||
flat: true,
|
||||
label: "CANCEL",
|
||||
color: this.theme=="dark"?"white":"dark"
|
||||
}
|
||||
}).then(password => {
|
||||
this.$store.commit("gateway/set_tx_status", {
|
||||
code: 1,
|
||||
message: "Sending transaction",
|
||||
sending: true
|
||||
})
|
||||
let newTx = objectAssignDeep.noMutate(this.newTx, {password})
|
||||
const newTx = objectAssignDeep.noMutate(this.newTx, {password})
|
||||
this.$gateway.send("wallet", "transfer", newTx)
|
||||
}).catch(() => {
|
||||
})
|
||||
}
|
||||
},
|
||||
mixins: [WalletPassword],
|
||||
components: {
|
||||
Identicon
|
||||
Identicon,
|
||||
LokiField
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style lang="scss">
|
||||
.send {
|
||||
.send-btn {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
40
src/pages/wallet/service-node.vue
Normal file
|
@ -0,0 +1,40 @@
|
|||
<template>
|
||||
<q-page class="service-node-page">
|
||||
<div class="header row items-center justify-center q-mt-md">
|
||||
<q-btn-toggle
|
||||
v-model="screen"
|
||||
toggle-color="primary"
|
||||
color="secondary"
|
||||
:options="[
|
||||
{label: 'Staking', value: 'staking'},
|
||||
{label: 'Registration', value: 'registration'},
|
||||
{label: 'Unlock', value: 'unlock'}
|
||||
]"
|
||||
/>
|
||||
</div>
|
||||
<ServiceNodeStaking v-if="screen === 'staking'"/>
|
||||
<ServiceNodeRegistration v-if="screen === 'registration'" />
|
||||
<ServiceNodeUnlock v-if="screen === 'unlock'" />
|
||||
</q-page>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ServiceNodeStaking from "components/service_node_staking"
|
||||
import ServiceNodeRegistration from "components/service_node_registration"
|
||||
import ServiceNodeUnlock from "components/service_node_unlock"
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
screen: "staking",
|
||||
}
|
||||
},
|
||||
components: {
|
||||
ServiceNodeStaking,
|
||||
ServiceNodeRegistration,
|
||||
ServiceNodeUnlock
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
</style>
|
|
@ -1,26 +1,27 @@
|
|||
<template>
|
||||
<q-page>
|
||||
|
||||
<div class="row q-pt-sm q-mx-md q-mb-sm items-center non-selectable">
|
||||
<div class="row q-pt-sm q-mx-md q-mb-sm items-end non-selectable">
|
||||
|
||||
<div class="col-5">
|
||||
<q-icon name="history" size="24px" /> Transaction history
|
||||
Transactions
|
||||
</div>
|
||||
|
||||
<div class="col-5 q-px-sm">
|
||||
<LokiField class="col-5 q-px-sm" label="Filter by txid">
|
||||
<q-input v-model="tx_txid"
|
||||
stack-label="Filter by txid"
|
||||
:dark="theme=='dark'"
|
||||
placeholder="Enter an ID"
|
||||
hide-underline
|
||||
/>
|
||||
</div>
|
||||
</LokiField>
|
||||
|
||||
<div class="col-2">
|
||||
<LokiField class="col-2" label="Filter by transaction type">
|
||||
<q-select :dark="theme=='dark'"
|
||||
v-model="tx_type"
|
||||
float-label="Filter by transaction type"
|
||||
:options="tx_type_options"
|
||||
hide-underline
|
||||
/>
|
||||
</div>
|
||||
</LokiField>
|
||||
|
||||
</div>
|
||||
<TxList :type="tx_type" :txid="tx_txid" />
|
||||
|
@ -30,6 +31,7 @@
|
|||
<script>
|
||||
import { mapState } from "vuex"
|
||||
import TxList from "components/tx_list"
|
||||
import LokiField from "components/loki_field"
|
||||
export default {
|
||||
data () {
|
||||
return {
|
||||
|
@ -39,8 +41,11 @@ export default {
|
|||
{label: "All", value: "all"},
|
||||
{label: "Incoming", value: "in"},
|
||||
{label: "Outgoing", value: "out"},
|
||||
{label: "Pending incoming", value: "pool"},
|
||||
{label: "Pending outgoing", value: "pending"},
|
||||
{label: "Pending", value: "all_pending"},
|
||||
{label: "Miner", value: "miner"},
|
||||
{label: "Service Node", value: "snode"},
|
||||
{label: "Governance", value: "gov"},
|
||||
{label: "Stake", value: "stake"},
|
||||
{label: "Failed", value: "failed"},
|
||||
]
|
||||
|
||||
|
@ -52,7 +57,8 @@ export default {
|
|||
}),
|
||||
|
||||
components: {
|
||||
TxList
|
||||
TxList,
|
||||
LokiField
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,15 +1,17 @@
|
|||
/**
|
||||
This is an unused class in LOKI
|
||||
*/
|
||||
|
||||
<template>
|
||||
<q-page padding>
|
||||
|
||||
<AddressHeader :address="info.address" :title="info.name" />
|
||||
|
||||
<div class="row">
|
||||
|
||||
<div class="infoBoxBalance">
|
||||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="info.balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="info.balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -18,7 +20,7 @@
|
|||
<div class="infoBox">
|
||||
<div class="infoBoxContent">
|
||||
<div class="text"><span>Unlocked balance</span></div>
|
||||
<div class="value"><span><FormatRyo :amount="info.unlocked_balance" /></span></div>
|
||||
<div class="value"><span><FormatLoki :amount="info.unlocked_balance" /></span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -262,7 +264,7 @@
|
|||
const { clipboard } = require("electron")
|
||||
import { mapState } from "vuex"
|
||||
import AddressHeader from "components/address_header"
|
||||
import FormatRyo from "components/format_ryo"
|
||||
import FormatLoki from "components/format_loki"
|
||||
import TxList from "components/tx_list"
|
||||
export default {
|
||||
computed: mapState({
|
||||
|
@ -299,7 +301,7 @@ export default {
|
|||
}
|
||||
}
|
||||
},
|
||||
mounted() {
|
||||
mounted () {
|
||||
const path = require("path")
|
||||
this.modals.key_image.export_path = path.join(this.data_dir, "gui")
|
||||
this.modals.key_image.import_path = path.join(this.data_dir, "gui", "key_image_export")
|
||||
|
@ -545,7 +547,7 @@ export default {
|
|||
}
|
||||
},
|
||||
components: {
|
||||
FormatRyo,
|
||||
FormatLoki,
|
||||
AddressHeader,
|
||||
TxList
|
||||
},
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import VueTimeago from 'vue-timeago'
|
||||
import VueTimeago from "vue-timeago"
|
||||
export default ({
|
||||
app,
|
||||
router,
|
||||
|
@ -6,7 +6,7 @@ export default ({
|
|||
Vue
|
||||
}) => {
|
||||
Vue.use(VueTimeago, {
|
||||
name: 'Timeago',
|
||||
locale: 'en'
|
||||
name: "Timeago",
|
||||
locale: "en"
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import Vuelidate from 'vuelidate'
|
||||
import Vuelidate from "vuelidate"
|
||||
|
||||
export default ({ Vue }) => {
|
||||
Vue.use(Vuelidate)
|
||||
|
|
|
@ -2,115 +2,121 @@ export default [
|
|||
{
|
||||
path: "/",
|
||||
component: () =>
|
||||
import ("layouts/init/loading"),
|
||||
import("layouts/init/loading"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/init/index")
|
||||
import("pages/init/index")
|
||||
},
|
||||
{
|
||||
path: "/quit",
|
||||
component: () =>
|
||||
import ("pages/init/quit")
|
||||
},
|
||||
import("pages/init/quit")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/welcome",
|
||||
component: () =>
|
||||
import ("layouts/init/welcome"),
|
||||
import("layouts/init/welcome"),
|
||||
children: [{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/init/welcome")
|
||||
import("pages/init/welcome")
|
||||
}]
|
||||
},
|
||||
{
|
||||
path: "/wallet-select",
|
||||
component: () =>
|
||||
import ("layouts/wallet-select/main"),
|
||||
import("layouts/wallet-select/main"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
name: "wallet-select",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/index")
|
||||
import("pages/wallet-select/index")
|
||||
},
|
||||
{
|
||||
path: "create",
|
||||
name: "wallet-create",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/create")
|
||||
import("pages/wallet-select/create")
|
||||
},
|
||||
{
|
||||
path: "restore",
|
||||
name: "wallet-restore",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/restore")
|
||||
import("pages/wallet-select/restore")
|
||||
},
|
||||
{
|
||||
path: "import-view-only",
|
||||
name: "wallet-import-view-only",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import-view-only")
|
||||
import("pages/wallet-select/import-view-only")
|
||||
},
|
||||
{
|
||||
path: "import",
|
||||
name: "wallet-import",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import")
|
||||
import("pages/wallet-select/import")
|
||||
},
|
||||
{
|
||||
path: "import-legacy",
|
||||
name: "wallet-import-legacy",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/import-legacy")
|
||||
import("pages/wallet-select/import-legacy")
|
||||
},
|
||||
{
|
||||
path: "created",
|
||||
name: "wallet-created",
|
||||
component: () =>
|
||||
import ("pages/wallet-select/created")
|
||||
import("pages/wallet-select/created")
|
||||
},
|
||||
{
|
||||
path: "import-old-gui",
|
||||
name: "wallet-import-old-gui",
|
||||
component: () =>
|
||||
import("pages/wallet-select/import-old-gui")
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: "/wallet",
|
||||
component: () =>
|
||||
import ("layouts/wallet/main"),
|
||||
import("layouts/wallet/main"),
|
||||
children: [
|
||||
{
|
||||
path: "",
|
||||
component: () =>
|
||||
import ("pages/wallet/wallet")
|
||||
import("pages/wallet/txhistory")
|
||||
},
|
||||
{
|
||||
path: "receive",
|
||||
component: () =>
|
||||
import ("pages/wallet/receive")
|
||||
import("pages/wallet/receive")
|
||||
},
|
||||
{
|
||||
path: "send",
|
||||
component: () =>
|
||||
import ("pages/wallet/send")
|
||||
import("pages/wallet/send")
|
||||
},
|
||||
{
|
||||
path: "addressbook",
|
||||
component: () =>
|
||||
import ("pages/wallet/addressbook")
|
||||
import("pages/wallet/addressbook")
|
||||
},
|
||||
{
|
||||
path: "txhistory",
|
||||
path: "servicenode",
|
||||
component: () =>
|
||||
import ("pages/wallet/txhistory")
|
||||
},
|
||||
import("pages/wallet/service-node")
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{ // Always leave this as last one
|
||||
path: "*",
|
||||
component: () =>
|
||||
import ("pages/404")
|
||||
import("pages/404")
|
||||
}
|
||||
]
|
||||
|
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 51 KiB |
66
src/statics/loki.svg
Normal file
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Generator: Adobe Illustrator 22.0.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
viewBox="0 0 1000 368" style="enable-background:new 0 0 1000 368;" xml:space="preserve">
|
||||
<style type="text/css">
|
||||
.st0{fill:#FFFFFF;}
|
||||
.st1{fill:url(#SVGID_1_);}
|
||||
.st2{fill:#333333;}
|
||||
.st3{fill:url(#SVGID_2_);}
|
||||
.st4{fill:url(#SVGID_3_);}
|
||||
.st5{fill:#00263A;}
|
||||
.st6{fill:url(#SVGID_4_);}
|
||||
.st7{fill:url(#SVGID_5_);}
|
||||
.st8{fill:url(#SVGID_6_);}
|
||||
.st9{fill:url(#SVGID_7_);}
|
||||
.st10{fill:url(#SVGID_8_);}
|
||||
.st11{fill:url(#SVGID_9_);}
|
||||
.st12{fill:url(#SVGID_10_);}
|
||||
.st13{fill:url(#SVGID_11_);}
|
||||
.st14{fill:url(#SVGID_12_);}
|
||||
.st15{fill:url(#SVGID_13_);}
|
||||
.st16{fill:url(#SVGID_14_);}
|
||||
.st17{fill:url(#SVGID_15_);}
|
||||
.st18{fill:url(#SVGID_16_);}
|
||||
.st19{fill:url(#SVGID_17_);}
|
||||
.st20{opacity:6.000000e-02;}
|
||||
.st21{opacity:4.000000e-02;fill:#FFFFFF;}
|
||||
.st22{opacity:7.000000e-02;fill:#FFFFFF;}
|
||||
.st23{fill:#008522;}
|
||||
.st24{fill:#78BE20;}
|
||||
.st25{fill:#005F61;}
|
||||
.st26{fill:url(#SVGID_18_);}
|
||||
</style>
|
||||
<g>
|
||||
<path class="st0" d="M366.6,78h37.1v178.9H497v32.7H366.6V78z"/>
|
||||
<path class="st0" d="M619.8,74.5C683.3,74.5,728,120.8,728,184c0,63.1-44.7,109.5-108.2,109.5c-63.5,0-108.2-46.3-108.2-109.5
|
||||
C511.6,120.8,556.3,74.5,619.8,74.5z M619.8,107.5c-42.8,0-70.1,32.7-70.1,76.5c0,43.5,27.3,76.5,70.1,76.5
|
||||
c42.5,0,70.1-33,70.1-76.5C689.9,140.2,662.3,107.5,619.8,107.5z"/>
|
||||
<path class="st0" d="M819.4,200.5L801,222v67.6h-37.1V78H801v100.9L883.8,78h46l-86,99.9l92.3,111.7h-45.7L819.4,200.5z"/>
|
||||
<path class="st0" d="M960.9,78H998v211.6h-37.1V78z"/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_1_" gradientUnits="userSpaceOnUse" x1="86.8402" y1="268.7968" x2="86.8402" y2="0.426">
|
||||
<stop offset="0" style="stop-color:#78BE20"/>
|
||||
<stop offset="0.1197" style="stop-color:#58AF21"/>
|
||||
<stop offset="0.3682" style="stop-color:#199122"/>
|
||||
<stop offset="0.486" style="stop-color:#008522"/>
|
||||
<stop offset="0.6925" style="stop-color:#007242"/>
|
||||
<stop offset="0.8806" style="stop-color:#006459"/>
|
||||
<stop offset="1" style="stop-color:#005F61"/>
|
||||
</linearGradient>
|
||||
<polygon class="st1" points="132.1,268.8 0.3,137 136.9,0.4 173.3,36.8 73.1,137 168.5,232.4 "/>
|
||||
</g>
|
||||
<g>
|
||||
<linearGradient id="SVGID_2_" gradientUnits="userSpaceOnUse" x1="212.9564" y1="367.5197" x2="212.9564" y2="99.1484">
|
||||
<stop offset="0" style="stop-color:#78BE20"/>
|
||||
<stop offset="0.1197" style="stop-color:#58AF21"/>
|
||||
<stop offset="0.3682" style="stop-color:#199122"/>
|
||||
<stop offset="0.486" style="stop-color:#008522"/>
|
||||
<stop offset="0.6925" style="stop-color:#007242"/>
|
||||
<stop offset="0.8806" style="stop-color:#006459"/>
|
||||
<stop offset="1" style="stop-color:#005F61"/>
|
||||
</linearGradient>
|
||||
<polygon class="st3" points="162.9,367.5 126.5,331.1 226.7,230.9 131.3,135.6 167.7,99.1 299.5,230.9 "/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 2.9 KiB |
3
src/statics/qr-code-grey.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 401 401" width="401" height="401"><defs><path d="M0 401L0 218.72L182.27 218.72L182.27 401L0 401ZM36.45 364.26L145.82 364.26L145.82 255.18L36.45 255.18L36.45 364.26Z" id="cP4vhvhrf"></path><path d="M109.36 291.63C109.36 291.63 109.36 291.63 109.36 291.63C109.36 313.51 109.36 325.66 109.36 328.09C109.36 328.09 109.36 328.09 109.36 328.09C87.49 328.09 75.34 328.09 72.91 328.09C72.91 328.09 72.91 328.09 72.91 328.09C72.91 306.22 72.91 294.06 72.91 291.63C72.91 291.63 72.91 291.63 72.91 291.63C94.78 291.63 106.93 291.63 109.36 291.63Z" id="b3K4Wy5wBr"></path><path d="M328.09 364.55C328.09 364.55 328.09 364.55 328.09 364.55C328.09 386.42 328.09 398.57 328.09 401C328.09 401 328.09 401 328.09 401C306.21 401 294.06 401 291.63 401C291.63 401 291.63 401 291.63 401C291.63 379.13 291.63 366.98 291.63 364.55C291.63 364.55 291.63 364.55 291.63 364.55C313.51 364.55 325.66 364.55 328.09 364.55Z" id="ajtxNY2Qi"></path><path d="M401 364.55C401 364.55 401 364.55 401 364.55C401 386.42 401 398.57 401 401C401 401 401 401 401 401C379.12 401 366.97 401 364.54 401C364.54 401 364.54 401 364.54 401C364.54 379.13 364.54 366.98 364.54 364.55C364.54 364.55 364.54 364.55 364.54 364.55C386.42 364.55 398.57 364.55 401 364.55Z" id="b12z2Qj5i"></path><path d="M328.09 255.18L328.09 218.72L218.72 218.72L218.72 401L255.18 401L255.18 291.63L291.63 291.63L291.63 328.09L401 328.09L401 218.72L401 218.72L364.54 218.72L364.54 255.18L328.09 255.18Z" id="aDSxNIZuL"></path><path d="M0 182.28L0 0L182.27 0L182.27 182.28L0 182.28ZM36.45 145.82L145.82 145.82L145.82 36.45L36.45 36.45L36.45 145.82Z" id="bpcQxL4BR"></path><path d="M109.36 72.91C109.36 72.91 109.36 72.91 109.36 72.91C109.36 94.78 109.36 106.93 109.36 109.36C109.36 109.36 109.36 109.36 109.36 109.36C87.49 109.36 75.34 109.36 72.91 109.36C72.91 109.36 72.91 109.36 72.91 109.36C72.91 87.49 72.91 75.34 72.91 72.91C72.91 72.91 72.91 72.91 72.91 72.91C94.78 72.91 106.93 72.91 109.36 72.91Z" id="b2tkw7De1"></path><path d="M218.72 182.28L218.72 0L401 0L401 182.28L218.72 182.28ZM255.18 145.82L364.54 145.82L364.54 36.45L255.18 36.45L255.18 145.82Z" id="b3NlRRtUXl"></path><path d="M328.09 72.91C328.09 72.91 328.09 72.91 328.09 72.91C328.09 94.78 328.09 106.93 328.09 109.36C328.09 109.36 328.09 109.36 328.09 109.36C306.21 109.36 294.06 109.36 291.63 109.36C291.63 109.36 291.63 109.36 291.63 109.36C291.63 87.49 291.63 75.34 291.63 72.91C291.63 72.91 291.63 72.91 291.63 72.91C313.51 72.91 325.66 72.91 328.09 72.91Z" id="aiJPdIloT"></path></defs><g><g><g><use xlink:href="#cP4vhvhrf" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#cP4vhvhrf" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b3K4Wy5wBr" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b3K4Wy5wBr" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#ajtxNY2Qi" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#ajtxNY2Qi" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b12z2Qj5i" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b12z2Qj5i" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aDSxNIZuL" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#aDSxNIZuL" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bpcQxL4BR" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#bpcQxL4BR" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b2tkw7De1" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b2tkw7De1" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b3NlRRtUXl" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#b3NlRRtUXl" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#aiJPdIloT" opacity="1" fill="#bbbbbb" fill-opacity="1"></use><g><use xlink:href="#aiJPdIloT" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>
|
After Width: | Height: | Size: 4.6 KiB |
3
src/statics/qr-code.svg
Normal file
|
@ -0,0 +1,3 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" preserveAspectRatio="xMidYMid meet" viewBox="0 0 401 401" width="401" height="401"><defs><path d="M0 401L0 218.72L182.27 218.72L182.27 401L0 401ZM36.45 364.26L145.82 364.26L145.82 255.18L36.45 255.18L36.45 364.26Z" id="c4EjTiqRPu"></path><path d="M109.36 291.63C109.36 291.63 109.36 291.63 109.36 291.63C109.36 313.51 109.36 325.66 109.36 328.09C109.36 328.09 109.36 328.09 109.36 328.09C87.49 328.09 75.34 328.09 72.91 328.09C72.91 328.09 72.91 328.09 72.91 328.09C72.91 306.22 72.91 294.06 72.91 291.63C72.91 291.63 72.91 291.63 72.91 291.63C94.78 291.63 106.93 291.63 109.36 291.63Z" id="e1x2wlSIyH"></path><path d="M328.09 364.55C328.09 364.55 328.09 364.55 328.09 364.55C328.09 386.42 328.09 398.57 328.09 401C328.09 401 328.09 401 328.09 401C306.21 401 294.06 401 291.63 401C291.63 401 291.63 401 291.63 401C291.63 379.13 291.63 366.98 291.63 364.55C291.63 364.55 291.63 364.55 291.63 364.55C313.51 364.55 325.66 364.55 328.09 364.55Z" id="b2SEDxmpGS"></path><path d="M401 364.55C401 364.55 401 364.55 401 364.55C401 386.42 401 398.57 401 401C401 401 401 401 401 401C379.12 401 366.97 401 364.54 401C364.54 401 364.54 401 364.54 401C364.54 379.13 364.54 366.98 364.54 364.55C364.54 364.55 364.54 364.55 364.54 364.55C386.42 364.55 398.57 364.55 401 364.55Z" id="b1t3ctO9Ib"></path><path d="M328.09 255.18L328.09 218.72L218.72 218.72L218.72 401L255.18 401L255.18 291.63L291.63 291.63L291.63 328.09L401 328.09L401 218.72L401 218.72L364.54 218.72L364.54 255.18L328.09 255.18Z" id="f2X4EE7RDM"></path><path d="M0 182.28L0 0L182.27 0L182.27 182.28L0 182.28ZM36.45 145.82L145.82 145.82L145.82 36.45L36.45 36.45L36.45 145.82Z" id="c222nLedUh"></path><path d="M109.36 72.91C109.36 72.91 109.36 72.91 109.36 72.91C109.36 94.78 109.36 106.93 109.36 109.36C109.36 109.36 109.36 109.36 109.36 109.36C87.49 109.36 75.34 109.36 72.91 109.36C72.91 109.36 72.91 109.36 72.91 109.36C72.91 87.49 72.91 75.34 72.91 72.91C72.91 72.91 72.91 72.91 72.91 72.91C94.78 72.91 106.93 72.91 109.36 72.91Z" id="bfDGWJSeW"></path><path d="M218.72 182.28L218.72 0L401 0L401 182.28L218.72 182.28ZM255.18 145.82L364.54 145.82L364.54 36.45L255.18 36.45L255.18 145.82Z" id="a8V10SdpO"></path><path d="M328.09 72.91C328.09 72.91 328.09 72.91 328.09 72.91C328.09 94.78 328.09 106.93 328.09 109.36C328.09 109.36 328.09 109.36 328.09 109.36C306.21 109.36 294.06 109.36 291.63 109.36C291.63 109.36 291.63 109.36 291.63 109.36C291.63 87.49 291.63 75.34 291.63 72.91C291.63 72.91 291.63 72.91 291.63 72.91C313.51 72.91 325.66 72.91 328.09 72.91Z" id="eWcmpmYP8"></path></defs><g><g><g><use xlink:href="#c4EjTiqRPu" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#c4EjTiqRPu" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#e1x2wlSIyH" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#e1x2wlSIyH" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b2SEDxmpGS" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b2SEDxmpGS" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#b1t3ctO9Ib" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#b1t3ctO9Ib" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#f2X4EE7RDM" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#f2X4EE7RDM" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#c222nLedUh" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#c222nLedUh" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#bfDGWJSeW" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#bfDGWJSeW" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#a8V10SdpO" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#a8V10SdpO" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g><g><use xlink:href="#eWcmpmYP8" opacity="1" fill="#ffffff" fill-opacity="1"></use><g><use xlink:href="#eWcmpmYP8" opacity="1" fill-opacity="0" stroke="#000000" stroke-width="1" stroke-opacity="0"></use></g></g></g></g></svg>
|
After Width: | Height: | Size: 4.6 KiB |
|
@ -1,5 +1,4 @@
|
|||
export const resetWalletData = (state) => {
|
||||
|
||||
state.commit("set_wallet_data", {
|
||||
|
||||
status: {
|
||||
|
@ -20,23 +19,29 @@ export const resetWalletData = (state) => {
|
|||
spend_key: ""
|
||||
},
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
},
|
||||
address_list: {
|
||||
used: [],
|
||||
unused: [],
|
||||
address_book: [],
|
||||
address_book: []
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const resetWalletStatus = (state) => {
|
||||
state.commit("set_wallet_data", {
|
||||
status: {
|
||||
code: 1,
|
||||
message: null
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export const resetPendingConfig = (state) => {
|
||||
|
||||
state.commit("set_app_data", {
|
||||
|
||||
pending_config: state.state.app.config
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
@ -1,33 +1,31 @@
|
|||
export const isReady = (state) => {
|
||||
const { daemons, app } = state.app.config
|
||||
const config_daemon = daemons[app.net_type]
|
||||
|
||||
let target_height
|
||||
if(state.app.config.daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
if (config_daemon.type === "local") {
|
||||
target_height = Math.max(state.daemon.info.height, state.daemon.info.target_height)
|
||||
} else {
|
||||
target_height = state.daemon.info.height
|
||||
}
|
||||
|
||||
if(state.app.config.daemon.type === "local") {
|
||||
return state.daemon.info.is_ready && state.wallet.info.height >= target_height - 1
|
||||
} else {
|
||||
return state.wallet.info.height >= target_height - 1
|
||||
}
|
||||
return false
|
||||
return state.wallet.info.height >= target_height - 1
|
||||
}
|
||||
|
||||
export const isAbleToSend = (state) => {
|
||||
const { daemons, app } = state.app.config
|
||||
const config_daemon = daemons[app.net_type]
|
||||
|
||||
let target_height
|
||||
if(state.app.config.daemon.type === "local" && !state.daemon.info.is_ready) {
|
||||
if (config_daemon.type === "local") {
|
||||
target_height = Math.max(state.daemon.info.height, state.daemon.info.target_height)
|
||||
} else {
|
||||
target_height = state.daemon.info.height
|
||||
}
|
||||
|
||||
if(state.app.config.daemon.type === "local") {
|
||||
return state.daemon.info.is_ready && state.wallet.info.height >= target_height - 1
|
||||
} else if(state.app.config.daemon.type === "local_remote") {
|
||||
if (config_daemon.type === "local_remote") {
|
||||
return state.daemon.info.height_without_bootstrap >= target_height && state.wallet.info.height >= target_height - 1
|
||||
} else {
|
||||
return state.wallet.info.height >= target_height - 1
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -4,9 +4,9 @@ import * as mutations from "./mutations"
|
|||
import * as actions from "./actions"
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
namespaced: true,
|
||||
state,
|
||||
getters,
|
||||
mutations,
|
||||
actions
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
const objectAssignDeep = require("object-assign-deep");
|
||||
const objectAssignDeep = require("object-assign-deep")
|
||||
|
||||
export const set_app_data = (state, data) => {
|
||||
state.app = objectAssignDeep.noMutate(state.app, data)
|
||||
|
@ -12,6 +12,12 @@ export const set_wallet_data = (state, data) => {
|
|||
export const set_wallet_list = (state, data) => {
|
||||
state.wallets = objectAssignDeep.noMutate(state.wallets, data)
|
||||
}
|
||||
export const set_old_gui_import_status = (state, data) => {
|
||||
state.old_gui_import_status = data
|
||||
}
|
||||
export const set_tx_status = (state, data) => {
|
||||
state.tx_status = data
|
||||
}
|
||||
export const set_snode_status = (state, data) => {
|
||||
state.service_node_status = objectAssignDeep.noMutate(state.service_node_status, data)
|
||||
}
|
||||
|
|
|
@ -5,15 +5,24 @@ export default {
|
|||
},
|
||||
config: {
|
||||
appearance: {
|
||||
theme: "light"
|
||||
theme: "dark"
|
||||
}
|
||||
},
|
||||
pending_config: {
|
||||
},
|
||||
remotes: {
|
||||
}
|
||||
},
|
||||
wallets: {
|
||||
list: [],
|
||||
legacy: []
|
||||
legacy: [],
|
||||
|
||||
// List of wallets that are in a sub folder (format of the old GUI)
|
||||
directories: []
|
||||
},
|
||||
old_gui_import_status: {
|
||||
code: 0, // Success
|
||||
failed_wallets: []
|
||||
},
|
||||
wallet: {
|
||||
status: {
|
||||
|
@ -34,18 +43,35 @@ export default {
|
|||
spend_key: ""
|
||||
},
|
||||
transactions: {
|
||||
tx_list: [],
|
||||
tx_list: []
|
||||
},
|
||||
address_list: {
|
||||
used: [],
|
||||
unused: [],
|
||||
address_book: [],
|
||||
address_book: []
|
||||
}
|
||||
},
|
||||
tx_status: {
|
||||
code: 0,
|
||||
message: ""
|
||||
},
|
||||
service_node_status: {
|
||||
stake: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
},
|
||||
registration: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
},
|
||||
unlock: {
|
||||
code: 0,
|
||||
message: "",
|
||||
sending: false
|
||||
}
|
||||
},
|
||||
daemon: {
|
||||
info: {
|
||||
alt_blocks_count: 0,
|
||||
|
|
|
@ -1945,13 +1945,12 @@ return{_strlen:lb,_ge_mul8:Va,_keccak:db,_ge_scalarmult:Ta,_ge_fromfe_frombytes_
|
|||
|
||||
|
||||
|
||||
var ryoConfig = {
|
||||
coinUnitPlaces: 9,
|
||||
coinSymbol: 'RYO',
|
||||
coinName: 'Ryo',
|
||||
coinUriPrefix: 'ryo:',
|
||||
longAddrPrefix: 0x2ce192,
|
||||
kurzAddrPrefix: 0x2c6192
|
||||
var lokiConfig = {
|
||||
coinUnitPlaces: 12,
|
||||
coinSymbol: 'LOKI',
|
||||
coinName: 'Loki',
|
||||
coinUriPrefix: 'loki:',
|
||||
addressPrefix: 114,
|
||||
};
|
||||
|
||||
var cnUtilGen = function(initConfig) {
|
||||
|
@ -1962,7 +1961,7 @@ var cnUtilGen = function(initConfig) {
|
|||
var HASH_STATE_BYTES = 200;
|
||||
var HASH_SIZE = 32;
|
||||
var ADDRESS_CHECKSUM_SIZE = 4;
|
||||
var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.longAddrPrefix;
|
||||
var CRYPTONOTE_PUBLIC_ADDRESS_BASE58_PREFIX = config.addressPrefix;
|
||||
var UINT64_MAX = new JSBigInt(2).pow(64);
|
||||
var CURRENT_TX_VERSION = 1;
|
||||
var TX_EXTRA_NONCE_MAX_COUNT = 255;
|
||||
|
@ -2092,13 +2091,13 @@ var cnUtilGen = function(initConfig) {
|
|||
|
||||
this.pubkeys_to_string = function(spend, view, use_kurz) {
|
||||
if(!use_kurz) {
|
||||
var prefix = this.encode_varint(config.longAddrPrefix);
|
||||
var prefix = this.encode_varint(config.addressPrefix);
|
||||
var data = prefix + spend + view;
|
||||
var checksum = this.cn_fast_hash(data);
|
||||
return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2));
|
||||
}
|
||||
else {
|
||||
var prefix = this.encode_varint(config.kurzAddrPrefix);
|
||||
var prefix = this.encode_varint(config.addressPrefix);
|
||||
var data = prefix + spend;
|
||||
var checksum = this.cn_fast_hash(data);
|
||||
return cnBase58.encode(data + checksum.slice(0, ADDRESS_CHECKSUM_SIZE * 2));
|
||||
|
@ -2390,7 +2389,7 @@ var cnUtilGen = function(initConfig) {
|
|||
|
||||
return this;
|
||||
};
|
||||
var cnUtil = cnUtilGen(ryoConfig);
|
||||
var cnUtil = cnUtilGen(lokiConfig);
|
||||
/*
|
||||
mnemonic.js : Converts between 4-byte aligned strings and a human-readable
|
||||
sequence of words. Uses 1626 common words taken from wikipedia article:
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
//import { validateAddress } from "./address_tools"
|
||||
/* eslint-disable prefer-promise-reject-errors */
|
||||
|
||||
export const greater_than_zero = (input) => {
|
||||
return input > 0
|
||||
}
|
||||
|
||||
export const payment_id = (input) => {
|
||||
return input.length === 0 || (/^[0-9A-Fa-f]+$/.test(input) && (input.length == 16 || input.length == 64))
|
||||
|
@ -8,36 +12,28 @@ export const privkey = (input) => {
|
|||
return input.length === 0 || (/^[0-9A-Fa-f]+$/.test(input) && input.length == 64)
|
||||
}
|
||||
|
||||
export const address = (input) => {
|
||||
|
||||
if(!(/^[0-9A-Za-z]+$/.test(input))) return false
|
||||
|
||||
switch (input.substring(0,4)) {
|
||||
case "Sumo":
|
||||
case "RYoL":
|
||||
case "Suto":
|
||||
case "RYoT":
|
||||
return input.length === 99
|
||||
|
||||
case "Subo":
|
||||
case "Suso":
|
||||
return input.length == 98
|
||||
|
||||
case "RYoS":
|
||||
case "RYoU":
|
||||
return input.length == 99
|
||||
|
||||
case "Sumi":
|
||||
case "RYoN":
|
||||
case "Suti":
|
||||
case "RYoE":
|
||||
return input.length === 110
|
||||
|
||||
case "RYoK":
|
||||
case "RYoH":
|
||||
return input.length === 55
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
export const service_node_key = (input) => {
|
||||
return input.length === 64 && /^[0-9A-Za-z]+$/.test(input)
|
||||
}
|
||||
|
||||
export const address = (input, gateway) => {
|
||||
if (!(/^[0-9A-Za-z]+$/.test(input))) return false
|
||||
|
||||
// Validate the address
|
||||
return new Promise((resolve, reject) => {
|
||||
gateway.once("validate_address", (data) => {
|
||||
if (data.address && data.address !== input) {
|
||||
reject()
|
||||
} else {
|
||||
if (data.valid) {
|
||||
resolve()
|
||||
} else {
|
||||
reject()
|
||||
}
|
||||
}
|
||||
})
|
||||
gateway.send("wallet", "validate_address", {
|
||||
address: input
|
||||
})
|
||||
})
|
||||
}
|
||||
|
|