Browse Source

Merge branch 'master' of git://github.com/shesek/btc-rpc-explorer into shesek-master

fix-133-memory-crash
Dan Janosik 6 years ago
parent
commit
f74ade553a
  1. 5
      README.md
  2. 15
      app.js
  3. 13
      app/auth.js
  4. 24
      app/config.js
  5. 32
      app/defaultCredentials.js
  6. 2
      app/utils.js
  7. 69
      bin/cli.js
  8. 2
      bin/www
  9. 7
      package.json
  10. 41
      routes/baseActionsRouter.js
  11. 1
      views/browser.pug
  12. 2
      views/connect.pug
  13. 2
      views/layout.pug
  14. 1
      views/search.pug
  15. 3
      views/terminal.pug

5
README.md

@ -53,9 +53,12 @@ BTCEXP_BITCOIND_PORT = 8332
BTCEXP_BITCOIND_USER = username BTCEXP_BITCOIND_USER = username
BTCEXP_BITCOIND_PASS = password BTCEXP_BITCOIND_PASS = password
BTCEXP_IPSTACK_KEY = 0000aaaafffffgggggg BTCEXP_IPSTACK_KEY = 0000aaaafffffgggggg
BTCEXP_COOKIEPASSWORD = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f BTCEXP_COOKIE_SECRET = 0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f
``` ```
You may enable password protection by setting `BTCEXP_LOGIN=<password>`.
Authenticating is done with http basic auth, using the selected password and an empty (or any) username.
## Run via Docker ## Run via Docker
1. `docker build -t btc-rpc-explorer .` 1. `docker build -t btc-rpc-explorer .`

15
app.js

@ -11,6 +11,7 @@ var logger = require('morgan');
var cookieParser = require('cookie-parser'); var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser'); var bodyParser = require('body-parser');
var session = require("express-session"); var session = require("express-session");
var csurf = require("csurf");
var config = require("./app/config.js"); var config = require("./app/config.js");
var simpleGit = require('simple-git'); var simpleGit = require('simple-git');
var utils = require("./app/utils.js"); var utils = require("./app/utils.js");
@ -27,6 +28,7 @@ var fs = require('fs');
var electrumApi = require("./app/api/electrumApi.js"); var electrumApi = require("./app/api/electrumApi.js");
var Influx = require("influx"); var Influx = require("influx");
var coreApi = require("./app/api/coreApi.js"); var coreApi = require("./app/api/coreApi.js");
var auth = require('./app/auth.js');
var crawlerBotUserAgentStrings = [ "Googlebot", "Bingbot", "Slurp", "DuckDuckBot", "Baiduspider", "YandexBot", "Sogou", "Exabot", "facebot", "ia_archiver" ]; var crawlerBotUserAgentStrings = [ "Googlebot", "Bingbot", "Slurp", "DuckDuckBot", "Baiduspider", "YandexBot", "Sogou", "Exabot", "facebot", "ia_archiver" ];
@ -46,6 +48,12 @@ app.engine('pug', (path, options, fn) => {
app.set('view engine', 'pug'); app.set('view engine', 'pug');
// basic http authentication
if (process.env.BTCEXP_LOGIN) {
app.disable('x-powered-by');
app.use(auth(process.env.BTCEXP_LOGIN));
}
// uncomment after placing your favicon in /public // uncomment after placing your favicon in /public
//app.use(favicon(__dirname + '/public/favicon.ico')); //app.use(favicon(__dirname + '/public/favicon.ico'));
app.use(logger('dev')); app.use(logger('dev'));
@ -53,7 +61,7 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false })); app.use(bodyParser.urlencoded({ extended: false }));
app.use(cookieParser()); app.use(cookieParser());
app.use(session({ app.use(session({
secret: config.cookiePassword, secret: config.cookieSecret,
resave: false, resave: false,
saveUninitialized: false saveUninitialized: false
})); }));
@ -447,6 +455,11 @@ app.use(function(req, res, next) {
next(); next();
}); });
app.use(csurf(), (req, res, next) => {
res.locals.csrfToken = req.csrfToken();
next();
});
app.use('/', baseActionsRouter); app.use('/', baseActionsRouter);
/// catch 404 and forwarding to error handler /// catch 404 and forwarding to error handler

13
app/auth.js

@ -0,0 +1,13 @@
var basicAuth = require('basic-auth');
module.exports = pass => (req, res, next) => {
var cred = basicAuth(req);
if (cred && cred.pass === pass) {
req.authenticated = true;
return next();
}
res.set('WWW-Authenticate', `Basic realm="Private Area"`)
.sendStatus(401);
}

24
app/config.js

@ -1,3 +1,5 @@
var fs = require('fs');
var crypto = require('crypto');
var coins = require("./coins.js"); var coins = require("./coins.js");
var currentCoin = process.env.BTCEXP_COIN || "BTC"; var currentCoin = process.env.BTCEXP_COIN || "BTC";
@ -7,12 +9,27 @@ try {
Object.assign(credentials, require("./credentials.js")) Object.assign(credentials, require("./credentials.js"))
} catch (err) {} } catch (err) {}
var rpcCred = credentials.rpc;
if (rpcCred.cookie && !rpcCred.username && !rpcCred.password && fs.existsSync(rpcCred.cookie)) {
[ rpcCred.username, rpcCred.password ] = fs.readFileSync(rpcCred.cookie).toString().split(':', 2);
if (!rpcCred.password) throw new Error('Cookie file '+rpcCred.cookie+' in unexpected format');
}
var cookieSecret = process.env.BTCEXP_COOKIE_SECRET
|| (rpcCred.password && crypto.createHmac('sha256', JSON.stringify(rpcCred))
.update('btc-rpc-explorer-cookie-secret').digest('hex'))
|| "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f";
module.exports = { module.exports = {
cookiePassword: process.env.BTCEXP_COOKIE_PASSWORD || "0x000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f", cookieSecret: cookieSecret,
demoSite: !!process.env.BTCEXP_DEMO, demoSite: !!process.env.BTCEXP_DEMO,
coin: currentCoin, coin: currentCoin,
rpcBlacklist:[ rpcBlacklist:
process.env.BTCEXP_RPC_ALLOWALL ? []
: process.env.BTCEXP_RPC_BLACKLIST ? process.env.BTCEXP_RPC_BLACKLIST.split(',').filter(Boolean)
: [
"addnode", "addnode",
"backupwallet", "backupwallet",
"bumpfee", "bumpfee",
@ -92,9 +109,6 @@ module.exports = {
credentials: credentials, credentials: credentials,
// Edit "ipWhitelistForRpcCommands" regex to limit access to RPC Browser / Terminal to matching IPs
ipWhitelistForRpcCommands:/^(127\.0\.0\.1)?(\:\:1)?$/,
siteTools:[ siteTools:[
{name:"Node Status", url:"/node-status", desc:"Summary of this node: version, network, uptime, etc.", fontawesome:"fas fa-broadcast-tower"}, {name:"Node Status", url:"/node-status", desc:"Summary of this node: version, network, uptime, etc.", fontawesome:"fas fa-broadcast-tower"},
{name:"Peers", url:"/peers", desc:"Detailed info about the peers connected to this node.", fontawesome:"fas fa-sitemap"}, {name:"Peers", url:"/peers", desc:"Detailed info about the peers connected to this node.", fontawesome:"fas fa-sitemap"},

32
app/defaultCredentials.js

@ -1,18 +1,30 @@
var os = require('os');
var path = require('path');
var url = require('url');
var btcUri = process.env.BTCEXP_BITCOIND_URI ? url.parse(process.env.BTCEXP_BITCOIND_URI, true) : { query: { } };
var btcAuth = btcUri.auth ? btcUri.auth.split(':') : [];
var ifxUri = process.env.BTCEXP_INFLUXDB_URI ? url.parse(process.env.BTCEXP_INFLUXDB_URI, true) : { query: { } };
var ifxAuth = ifxUri.auth ? ifxUri.auth.split(':') : [];
var ifxActive = !!process.env.BTCEXP_ENABLE_INFLUXDB || Object.keys(process.env).some(k => k.startsWith('BTCEXP_INFLUXDB_'));
module.exports = { module.exports = {
rpc: { rpc: {
host: process.env.BTCEXP_BITCOIND_HOST || "127.0.0.1", host: btcUri.hostname || process.env.BTCEXP_BITCOIND_HOST || "127.0.0.1",
port: process.env.BTCEXP_BITCOIND_PORT || 8332, port: btcUri.port || process.env.BTCEXP_BITCOIND_PORT || 8332,
username: process.env.BTCEXP_BITCOIND_USER, username: btcAuth[0] || process.env.BTCEXP_BITCOIND_USER,
password: process.env.BTCEXP_BITCOIND_PASS, password: btcAuth[1] || process.env.BTCEXP_BITCOIND_PASS,
cookie: btcUri.query.cookie || process.env.BTCEXP_BITCOIND_COOKIE || path.join(os.homedir(), '.bitcoin', '.cookie'),
}, },
influxdb:{ influxdb:{
active: !!process.env.BTCEXP_INFLUXDB_ENABLED, active: ifxActive,
host: process.env.BTCEXP_INFLUXDB_HOST || "127.0.0.1", host: ifxUri.hostname || process.env.BTCEXP_INFLUXDB_HOST || "127.0.0.1",
port: process.env.BTCEXP_INFLUXDB_PORT || 8086, port: ifxUri.port || process.env.BTCEXP_INFLUXDB_PORT || 8086,
database: process.env.BTCEXP_INFLUXDB_DB || "influxdb", database: ifxUri.pathname && ifxUri.pathname.substr(1) || process.env.BTCEXP_INFLUXDB_DBNAME || "influxdb",
username: process.env.BTCEXP_INFLUXDB_USER || "admin", username: ifxAuth[0] || process.env.BTCEXP_INFLUXDB_USER || "admin",
password: process.env.BTCEXP_INFLUXDB_PASS || "admin" password: ifxAuth[1] || process.env.BTCEXP_INFLUXDB_PASS || "admin"
}, },
// optional: enter your api access key from ipstack.com below // optional: enter your api access key from ipstack.com below

2
app/utils.js

@ -276,6 +276,8 @@ function getBlockTotalFeesFromCoinbaseTxAndBlockHeight(coinbaseTx, blockHeight)
} }
function refreshExchangeRates() { function refreshExchangeRates() {
if (process.env.BTCEXP_NO_RATES) return;
if (coins[config.coin].exchangeRateData) { if (coins[config.coin].exchangeRateData) {
request(coins[config.coin].exchangeRateData.jsonUrl, function(error, response, body) { request(coins[config.coin].exchangeRateData.jsonUrl, function(error, response, body) {
if (!error && response && response.statusCode && response.statusCode == 200) { if (!error && response && response.statusCode && response.statusCode == 200) {

69
bin/cli.js

@ -0,0 +1,69 @@
#!/usr/bin/env node
const args = require('meow')(`
Usage
$ btc-rpc-explorer [options]
Options
-p, --port <port> port to bind http server [default: 3002]
-l, --login <password> protect web interface with a password [default: no password]
--coin <coin> crypto-coin to enable [default: BTC]
-b, --bitcoind-uri <uri> connection URI for bitcoind rpc (overrides the options below)
-H, --bitcoind-host <host> hostname for bitcoind rpc [default: 127.0.0.1]
-P, --bitcoind-port <port> port for bitcoind rpc [default: 8332]
-c, --bitcoind-cookie <path> path to bitcoind cookie file [default: 8332]
-u, --bitcoind-user <user> username for bitcoind rpc [default: none]
-w, --bitcoind-pass <pass> password for bitcoind rpc [default: none]
--rpc-allowall allow all rpc commands [default: false]
--rpc-blacklist <methods> comma separated list of rpc commands to block [default: see in config.js]
--cookie-secret <secret> secret key for signed cookie hmac generation [default: hmac derive from bitcoind pass]
--demo enable demoSite mode [default: disabled]
--no-rates disable fetching of currency exchange rates [default: enabled]
--ipstack-key <key> api access key for ipstack (for geoip) [default: disabled]
--ganalytics-tracking <tid> tracking id for google analytics [default: disabled]
--sentry-url <sentry-url> sentry url [default: disabled]
--enable-influxdb enable influxdb for logging network stats [default: false]
--influxdb-uri <uri> connection URI for influxdb (overrides the options below)
--influxdb-host <host> hostname for influxdb [default: 127.0.0.1]
--influxdb-port <port> port for influxdb [default: 8086]
--influxdb-user <user> username for influxdb [default: admin]
--influxdb-pass <pass> password for influxdb [default: admin]
--influxdb-dbname <db> database name for influxdb [default: influxdb]
-e, --node-env <env> nodejs environment mode [default: production]
-h, --help output usage information
-v, --version output version number
Examples
$ btc-rpc-explorer --port 8080 --bitcoind-port 18443 --bitcoind-cookie ~/.bitcoin/regtest/.cookie
$ btc-rpc-explorer -p 8080 -P 18443 -c ~/.bitcoin/regtest.cookie
Or using connection URIs
$ btc-rpc-explorer -b bitcoin://bob:myPassword@127.0.0.1:18443/
$ btc-rpc-explorer -b bitcoin://127.0.0.1:18443/?cookie=$HOME/.bitcoin/regtest/.cookie
$ btc-rpc-explorer --influxdb-uri influx://bob:myPassword@127.0.0.1:8086/dbName
All options may also be specified as environment variables
$ BTCEXP_PORT=8080 BTCEXP_BITCOIND_PORT=18443 BTCEXP_BITCOIND_COOKIE=~/.bitcoin/regtest/.cookie btc-rpc-explorer
`, { flags: { port: {alias:'p'}, login: {alias:'l'}
, bitcoindUri: {alias:'b'}, bitcoindHost: {alias:'H'}, bitcoindPort: {alias:'P'}
, bitcoindCookie: {alias:'c'}, bitcoindUser: {alias:'u'}, bitcoindPass: {alias:'w'}
, demo: {type:'boolean'}, rpcAllowall: {type:'boolean'}
, enableInfluxdb: {type:'boolean'}, nodeEnv: {alias:'e', default:'production'}
} }
).flags;
const envify = k => k.replace(/([A-Z])/g, '_$1').toUpperCase();
Object.keys(args).filter(k => k.length > 1).forEach(k => {
if (args[k] === false) process.env[`BTCEXP_NO_${envify(k)}`] = true;
else process.env[`BTCEXP_${envify(k)}`] = args[k];
})
require('./www');

2
bin/www

@ -2,7 +2,7 @@
var debug = require('debug')('my-application'); var debug = require('debug')('my-application');
var app = require('../app'); var app = require('../app');
app.set('port', process.env.PORT || 3002); app.set('port', process.env.PORT || process.env.BTCEXP_PORT || 3002);
var server = app.listen(app.get('port'), function() { var server = app.listen(app.get('port'), function() {
debug('Express server listening on port ' + server.address().port); debug('Express server listening on port ' + server.address().port);

7
package.json

@ -3,10 +3,12 @@
"version": "1.0.0", "version": "1.0.0",
"description": "Explorer for Bitcoin and RPC-compatible blockchains", "description": "Explorer for Bitcoin and RPC-compatible blockchains",
"private": false, "private": false,
"bin": "bin/cli.js",
"scripts": { "scripts": {
"start": "node ./bin/www", "start": "node ./bin/www",
"build": "npm-run-all build:*", "build": "npm-run-all build:*",
"build:less": "lessc ./public/css/radial-progress.less ./public/css/radial-progress.css" "build:less": "lessc ./public/css/radial-progress.less ./public/css/radial-progress.css",
"prepare": "npm run build"
}, },
"keywords": [ "keywords": [
"bitcoin", "bitcoin",
@ -20,11 +22,13 @@
"url": "git+https://github.com/janoside/btc-rpc-explorer.git" "url": "git+https://github.com/janoside/btc-rpc-explorer.git"
}, },
"dependencies": { "dependencies": {
"basic-auth": "^2.0.1",
"bitcoin-core": "2.0.0", "bitcoin-core": "2.0.0",
"bitcoinjs-lib": "3.3.2", "bitcoinjs-lib": "3.3.2",
"body-parser": "~1.18.2", "body-parser": "~1.18.2",
"cookie-parser": "~1.4.3", "cookie-parser": "~1.4.3",
"crypto-js": "3.1.9-1", "crypto-js": "3.1.9-1",
"csurf": "^1.9.0",
"debug": "~2.6.0", "debug": "~2.6.0",
"decimal.js": "7.2.3", "decimal.js": "7.2.3",
"dotenv": "^6.2.0", "dotenv": "^6.2.0",
@ -34,6 +38,7 @@
"influx": "5.0.7", "influx": "5.0.7",
"jstransformer-markdown-it": "^2.0.0", "jstransformer-markdown-it": "^2.0.0",
"lru-cache": "4.1.3", "lru-cache": "4.1.3",
"meow": "^5.0.0",
"moment": "^2.24.0", "moment": "^2.24.0",
"moment-duration-format": "2.2.2", "moment-duration-format": "2.2.2",
"morgan": "^1.9.1", "morgan": "^1.9.1",

41
routes/baseActionsRouter.js

@ -1,4 +1,5 @@
var express = require('express'); var express = require('express');
var csurf = require('csurf');
var router = express.Router(); var router = express.Router();
var util = require('util'); var util = require('util');
var moment = require('moment'); var moment = require('moment');
@ -14,6 +15,8 @@ var coins = require("./../app/coins.js");
var config = require("./../app/config.js"); var config = require("./../app/config.js");
var coreApi = require("./../app/api/coreApi.js"); var coreApi = require("./../app/api/coreApi.js");
const forceCsrf = csurf({ ignoreMethods: [] });
router.get("/", function(req, res) { router.get("/", function(req, res) {
if (req.session.host == null || req.session.host.trim() == "") { if (req.session.host == null || req.session.host.trim() == "") {
if (req.cookies['rpc-host']) { if (req.cookies['rpc-host']) {
@ -41,7 +44,7 @@ router.get("/", function(req, res) {
promises.push(coreApi.getMempoolInfo()); promises.push(coreApi.getMempoolInfo());
promises.push(coreApi.getMiningInfo()); promises.push(coreApi.getMiningInfo());
var chainTxStatsIntervals = [ 144, 144 * 7, 144 * 30, 144 * 265 ]; var chainTxStatsIntervals = [ 144, 144 * 7, 144 * 30, 144 * 365 ];
res.locals.chainTxStatsLabels = [ "24 hours", "1 week", "1 month", "1 year", "All time" ]; res.locals.chainTxStatsLabels = [ "24 hours", "1 week", "1 month", "1 year", "All time" ];
for (var i = 0; i < chainTxStatsIntervals.length; i++) { for (var i = 0; i < chainTxStatsIntervals.length; i++) {
promises.push(coreApi.getChainTxStats(chainTxStatsIntervals[i])); promises.push(coreApi.getChainTxStats(chainTxStatsIntervals[i]));
@ -743,31 +746,19 @@ router.get("/address/:address", function(req, res) {
}); });
router.get("/rpc-terminal", function(req, res) { router.get("/rpc-terminal", function(req, res) {
if (!config.demoSite) { if (!config.demoSite && !req.authenticated) {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; res.send("RPC Terminal / Browser may not be accessed without logging-in. This restriction can be modified in your config.js file.");
var match = config.ipWhitelistForRpcCommands.exec(ip);
if (!match) {
res.send("RPC Terminal / Browser may not be accessed from '" + ip + "'. This restriction can be modified in your config.js file.");
return; return;
} }
}
res.render("terminal"); res.render("terminal");
}); });
router.post("/rpc-terminal", function(req, res) { router.post("/rpc-terminal", function(req, res) {
if (!config.demoSite) { if (!config.demoSite && !req.authenticated) {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; res.send("RPC Terminal / Browser may not be accessed without logging-in. This restriction can be modified in your config.js file.");
var match = config.ipWhitelistForRpcCommands.exec(ip);
if (!match) {
res.send("RPC Terminal / Browser may not be accessed from '" + ip + "'. This restriction can be modified in your config.js file.");
return; return;
} }
}
var params = req.body.cmd.trim().split(/\s+/); var params = req.body.cmd.trim().split(/\s+/);
var cmd = params.shift(); var cmd = params.shift();
@ -815,17 +806,11 @@ router.post("/rpc-terminal", function(req, res) {
}); });
}); });
router.get("/rpc-browser", function(req, res) { router.get("/rpc-browser", function(req, res, next) {
if (!config.demoSite) { if (!config.demoSite && !req.authenticated) {
var ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress; res.send("RPC Terminal / Browser may not be accessed without logging-in. This restriction can be modified in your config.js file.");
var match = config.ipWhitelistForRpcCommands.exec(ip);
if (!match) {
res.send("RPC Terminal / Browser may not be accessed from '" + ip + "'. This restriction can be modified in your config.js file.");
return; return;
} }
}
coreApi.getHelp().then(function(result) { coreApi.getHelp().then(function(result) {
res.locals.gethelp = result; res.locals.gethelp = result;
@ -883,6 +868,9 @@ router.get("/rpc-browser", function(req, res) {
return; return;
} }
forceCsrf(req, res, err => {
if (err) return next(err);
console.log("Executing RPC '" + req.query.method + "' with params: [" + argValues + "]"); console.log("Executing RPC '" + req.query.method + "' with params: [" + argValues + "]");
client.command([{method:req.query.method, parameters:argValues}], function(err3, result3, resHeaders3) { client.command([{method:req.query.method, parameters:argValues}], function(err3, result3, resHeaders3) {
@ -904,6 +892,7 @@ router.get("/rpc-browser", function(req, res) {
res.render("browser"); res.render("browser");
}); });
});
} else { } else {
res.render("browser"); res.render("browser");
} }

1
views/browser.pug

@ -46,6 +46,7 @@ block content
hr hr
form(method="get") form(method="get")
input(type="hidden", name="_csrf", value=csrfToken)
input(type="hidden", name="method", value=method) input(type="hidden", name="method", value=method)
div(class="h5 mb-3") Arguments div(class="h5 mb-3") Arguments

2
views/connect.pug

@ -5,6 +5,8 @@ block content
hr hr
form(method="post", action="/connect") form(method="post", action="/connect")
input(type="hidden", name="_csrf", value=csrfToken)
div(class="form-group") div(class="form-group")
label(for="input-host") Host / IP label(for="input-host") Host / IP
input(id="input-host", type="text", name="host", class="form-control", placeholder="Host / IP", value=host) input(id="input-host", type="text", name="host", class="form-control", placeholder="Host / IP", value=host)

2
views/layout.pug

@ -2,6 +2,7 @@ doctype html
html(lang="en") html(lang="en")
head head
meta(charset="utf-8") meta(charset="utf-8")
meta(name="csrf-token", content=csrfToken)
meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no") meta(name="viewport", content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no")
if (session.uiTheme && session.uiTheme == "dark") if (session.uiTheme && session.uiTheme == "dark")
@ -86,6 +87,7 @@ html(lang="en")
span Dark span Dark
form(method="post", action="/search", class="form-inline") form(method="post", action="/search", class="form-inline")
input(type="hidden", name="_csrf", value=csrfToken)
div(class="input-group input-group-sm") div(class="input-group input-group-sm")
input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;") input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;")
div(class="input-group-append") div(class="input-group-append")

1
views/search.pug

@ -9,6 +9,7 @@ block content
div(class="mb-5") div(class="mb-5")
form(method="post", action="/search", class="form") form(method="post", action="/search", class="form")
input(type="hidden", name="_csrf", value=csrfToken)
div(class="input-group input-group-lg") div(class="input-group input-group-lg")
input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;") input(type="text", class="form-control form-control-sm", name="query", placeholder="block height/hash, txid, address", value=(query), style="width: 300px;")
div(class="input-group-append") div(class="input-group-append")

3
views/terminal.pug

@ -33,6 +33,8 @@ block content
block endOfBody block endOfBody
script. script.
var csrfToken = $('meta[name=csrf-token]').attr('content');
$(document).ready(function() { $(document).ready(function() {
$("#terminal-form").submit(function(e) { $("#terminal-form").submit(function(e) {
e.preventDefault(); e.preventDefault();
@ -41,6 +43,7 @@ block endOfBody
var postData = {}; var postData = {};
postData.cmd = cmd; postData.cmd = cmd;
postData._csrf = csrfToken;
$.post( $.post(
"/rpc-terminal", "/rpc-terminal",

Loading…
Cancel
Save