mirror of
https://github.com/janoside/btc-rpc-explorer.git
synced 2026-08-13 12:33:13 +02:00
Various frontend asset changes
-support for accessing resources via a configurable CDN (probably cloudfront, backed by S3, as I plan to use for the public demo site) -move resource integrity hashes into a single file (generated by "npm run integrity") for easier maintenance -cleanup some startup logic -more updated dependencies
This commit is contained in:
parent
a0bb1e4fb2
commit
5740b0572b
13 changed files with 2088 additions and 495 deletions
21
.env-sample
21
.env-sample
|
|
@ -193,3 +193,24 @@
|
|||
# For slow devices reduce this number.
|
||||
# Default: 50
|
||||
#BTCEXP_UI_BLOCKS_PAGE_BLOCK_COUNT=50
|
||||
|
||||
|
||||
|
||||
#########
|
||||
|
||||
|
||||
# Options designed for production use, on public instances like the one at BitcoinExplorer.org. These tend to be cost-reduction-oriented in nature.
|
||||
|
||||
# S3 details for uploading assets to be served via CloudFront
|
||||
|
||||
# This is the optional profile name that the AWS SDK will use to load credentials. By default this refers to an item in ~/.aws/credentials
|
||||
#BTCEXP_S3_PROFILE_NAME=xxx
|
||||
|
||||
# The S3 bucket where assets will be uploaded on launch
|
||||
#BTCEXP_S3_BUCKET=xxx
|
||||
|
||||
# The path in the above S3 bucket where assets will be uploaded on launch
|
||||
#BTCEXP_S3_BUCKET_PATH=xxx/
|
||||
|
||||
# CDN base url; if S3 details are given, this will probably be a CloudFront path for assets that are uploaded at launch
|
||||
#BTCEXP_CDN_BASE_URL=xxx
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
##### v3.?
|
||||
###### Unreleased
|
||||
|
||||
* Minor fixes preparing for Bitcoin Core v23
|
||||
* Minor fixes for running against Bitcoin Core v23
|
||||
* Support for serving static assets via a configurable CDN
|
||||
* Misc fixes for erroneous data display on non-mainnet nodes
|
||||
* UI/UX tweak, especially on homepage
|
||||
* Updated dependencies
|
||||
|
|
|
|||
164
app.js
164
app.js
|
|
@ -96,6 +96,11 @@ const markdown = require("markdown-it")();
|
|||
const v8 = require("v8");
|
||||
var compression = require("compression");
|
||||
|
||||
const appUtils = require("@janoside/app-utils");
|
||||
const s3Utils = appUtils.s3Utils;
|
||||
|
||||
const s3Bucket = s3Utils.createBucket(config.cdn.s3Bucket, config.cdn.s3BucketPath);
|
||||
|
||||
require("./app/currencies.js");
|
||||
|
||||
const package_json = require('./package.json');
|
||||
|
|
@ -211,6 +216,51 @@ if (config.baseUrl != '/') {
|
|||
expressApp.get('/', (req, res) => res.redirect(config.baseUrl));
|
||||
}
|
||||
|
||||
|
||||
// if a CDN is configured, these assets will be uploaded at launch, then referenced from there
|
||||
const cdnItems = [
|
||||
[`style/dark.css`, `text/css`, "utf8"],
|
||||
[`style/light.css`, `text/css`, "utf8"],
|
||||
[`style/highlight.min.css`, `text/css`, "utf8"],
|
||||
[`style/dataTables.bootstrap4.min.css`, `text/css`, "utf8"],
|
||||
|
||||
[`js/bootstrap.bundle.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/chart.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/fontawesome.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/jquery.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/site.js`, `text/javascript`, "utf8"],
|
||||
[`js/highlight.pack.js`, `text/javascript`, "utf8"],
|
||||
[`js/chartjs-adapter-moment.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/jquery.dataTables.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/dataTables.bootstrap4.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/moment.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/sentry.min.js`, `text/javascript`, "utf8"],
|
||||
[`js/decimal.js`, `text/javascript`, "utf8"],
|
||||
|
||||
[`img/logo/logo.svg`, `image/svg+xml`, "utf8"],
|
||||
[`img/logo/mainnet/logo.svg`, `image/svg+xml`, "utf8"],
|
||||
[`img/logo/mainnet/apple-touch-icon.png`, `image/png`, "binary"],
|
||||
[`img/logo/mainnet/favicon-16x16.png`, `image/png`, "binary"],
|
||||
[`img/logo/mainnet/favicon-32x32.png`, `image/png`, "binary"],
|
||||
[`img/logo/testnet/logo.svg`, `image/svg+xml`, "utf8"],
|
||||
[`img/logo/signet/logo.svg`, `image/svg+xml`, "utf8"],
|
||||
[`img/logo/regtest/logo.svg`, `image/svg+xml`, "utf8"],
|
||||
|
||||
[`img/logo/mainnet/favicon.ico`, `image/x-icon`, "binary"],
|
||||
[`img/logo/testnet/favicon.ico`, `image/x-icon`, "binary"],
|
||||
[`img/logo/signet/favicon.ico`, `image/x-icon`, "binary"],
|
||||
[`img/logo/regtest/favicon.ico`, `image/x-icon`, "binary"],
|
||||
|
||||
[`leaflet/leaflet.js`, `text/javascript`, "utf8"],
|
||||
[`leaflet/leaflet.css`, `text/css`, "utf8"],
|
||||
];
|
||||
|
||||
const cdnFilepathMap = {};
|
||||
cdnItems.forEach(item => {
|
||||
cdnFilepathMap[item[0]] = true;
|
||||
});
|
||||
|
||||
|
||||
process.on("unhandledRejection", (reason, p) => {
|
||||
debugLog("Unhandled Rejection at: Promise", p, "reason:", reason, "stack:", (reason != null ? reason.stack : "null"));
|
||||
});
|
||||
|
|
@ -589,7 +639,7 @@ function refreshNetworkVolumes() {
|
|||
}
|
||||
|
||||
|
||||
expressApp.onStartup = function() {
|
||||
expressApp.onStartup = async () => {
|
||||
global.appStartTime = new Date().getTime();
|
||||
|
||||
global.config = config;
|
||||
|
|
@ -626,27 +676,29 @@ expressApp.onStartup = function() {
|
|||
|
||||
|
||||
if (global.sourcecodeVersion == null && fs.existsSync('.git')) {
|
||||
simpleGit(".").log(["-n 1"], function(err, log) {
|
||||
if (err) {
|
||||
utils.logError("3fehge9ee", err, {desc:"Error accessing git repo"});
|
||||
try {
|
||||
let log = await simpleGit(".").log(["-n 1"]);
|
||||
|
||||
global.cacheId = global.appVersion;
|
||||
debugLog(`Error getting sourcecode version, continuing to use default cacheId '${global.cacheId}'`);
|
||||
global.sourcecodeVersion = log.all[0].hash.substring(0, 10);
|
||||
global.sourcecodeDate = log.all[0].date.substring(0, "0000-00-00".length);
|
||||
|
||||
debugLog(`Starting ${global.coinConfig.ticker} RPC Explorer, v${global.appVersion} (code: unknown commit) at http://${config.host}:${config.port}${config.baseUrl}`);
|
||||
global.cacheId = `${global.sourcecodeDate}-${global.sourcecodeVersion}`;
|
||||
|
||||
} else {
|
||||
global.sourcecodeVersion = log.all[0].hash.substring(0, 10);
|
||||
global.cacheId = log.all[0].hash.substring(0, 10);
|
||||
global.sourcecodeDate = log.all[0].date.substring(0, "0000-00-00".length);
|
||||
debugLog(`Using sourcecode metadata as cacheId: '${global.cacheId}'`);
|
||||
|
||||
debugLog(`Using sourcecode version as cacheId: '${global.cacheId}'`);
|
||||
debugLog(`Starting ${global.coinConfig.ticker} RPC Explorer, v${global.appVersion} (commit: '${global.sourcecodeVersion}', date: ${global.sourcecodeDate}) at http://${config.host}:${config.port}${config.baseUrl}`);
|
||||
|
||||
debugLog(`Starting ${global.coinConfig.ticker} RPC Explorer, v${global.appVersion} (commit: '${global.sourcecodeVersion}', date: ${global.sourcecodeDate}) at http://${config.host}:${config.port}${config.baseUrl}`);
|
||||
}
|
||||
|
||||
expressApp.continueStartup();
|
||||
});
|
||||
} catch (err) {
|
||||
utils.logError("3fehge9ee", err, {desc:"Error accessing git repo"});
|
||||
|
||||
global.cacheId = global.appVersion;
|
||||
debugLog(`Error getting sourcecode version, continuing to use default cacheId '${global.cacheId}'`);
|
||||
|
||||
debugLog(`Starting ${global.coinConfig.ticker} RPC Explorer, v${global.appVersion} (code: unknown commit) at http://${config.host}:${config.port}${config.baseUrl}`);
|
||||
}
|
||||
|
||||
expressApp.continueStartup();
|
||||
|
||||
} else {
|
||||
global.cacheId = global.appVersion;
|
||||
|
|
@ -656,6 +708,60 @@ expressApp.onStartup = function() {
|
|||
|
||||
expressApp.continueStartup();
|
||||
}
|
||||
|
||||
if (config.cdn.active && config.cdn.s3Bucket) {
|
||||
debugLog(`Configuring CDN assets; uploading ${cdnItems.length} assets to S3...`);
|
||||
|
||||
const s3Path = (filepath) => { return `${global.cacheId}/${filepath}`; }
|
||||
|
||||
const uploadedItems = [];
|
||||
const existingItems = [];
|
||||
const errorItems = [];
|
||||
|
||||
const uploadAssetIfNeeded = async (filepath, contentType, encoding) => {
|
||||
try {
|
||||
let absoluteFilepath = path.join(process.cwd(), "public", filepath);
|
||||
let s3path = s3Path(filepath);
|
||||
|
||||
const existingAsset = await s3Bucket.get(s3path);
|
||||
|
||||
if (existingAsset) {
|
||||
existingItems.push(filepath);
|
||||
|
||||
//debugLog(`Asset ${filepath} already in S3, skipping upload.`);
|
||||
|
||||
} else {
|
||||
let fileData = fs.readFileSync(absoluteFilepath, {encoding: encoding, flag:'r'});
|
||||
let fileBuffer = new Buffer(fileData, encoding);
|
||||
|
||||
await s3Bucket.put(fileBuffer, s3path, contentType);
|
||||
|
||||
uploadedItems.push(filepath);
|
||||
|
||||
//debugLog(`Uploaded ${filepath} to S3.`);
|
||||
}
|
||||
} catch (e) {
|
||||
errorItems.push(filepath);
|
||||
|
||||
debugErrorLog(`Error uploading asset to S3: ${JSON.stringify(item)}`, e);
|
||||
}
|
||||
};
|
||||
|
||||
const promises = [];
|
||||
for (let i = 0; i < cdnItems.length; i++) {
|
||||
let item = cdnItems[i];
|
||||
|
||||
let filepath = item[0];
|
||||
let contentType = item[1];
|
||||
let encoding = item[2];
|
||||
|
||||
promises.push(uploadAssetIfNeeded(filepath, contentType, encoding));
|
||||
}
|
||||
|
||||
await utils.awaitPromises(promises);
|
||||
|
||||
debugLog(`Done uploading assets to S3:\n\tAlready present: ${existingItems.length}\n\tNewly uploaded: ${uploadedItems.length}\n\tError items: ${errorItems.length}`);
|
||||
}
|
||||
}
|
||||
|
||||
expressApp.continueStartup = function() {
|
||||
|
|
@ -940,6 +1046,32 @@ expressApp.locals.Decimal = Decimal;
|
|||
expressApp.locals.utils = utils;
|
||||
expressApp.locals.markdown = src => markdown.render(src);
|
||||
|
||||
expressApp.locals.assetUrl = (path) => {
|
||||
// trim off leading "./"
|
||||
let normalizedPath = path.substring(2);
|
||||
|
||||
//console.log("assetUrl: " + path + " -> " + normalizedPath);
|
||||
|
||||
if (config.cdn.active && cdnFilepathMap[normalizedPath]) {
|
||||
return `${config.cdn.baseUrl}/${global.cacheId}/${normalizedPath}`;
|
||||
|
||||
} else {
|
||||
return `${path}?v=${global.cacheId}`;
|
||||
}
|
||||
};
|
||||
|
||||
// debug setting to skip js/css integrity checks
|
||||
const skipIntegrityChecks = false;
|
||||
const resourceIntegrityHashes = JSON.parse(fs.readFileSync(path.join(process.cwd(), "public/txt/resource-integrity.json")));
|
||||
|
||||
expressApp.locals.assetIntegrity = (filename) => {
|
||||
if (!skipIntegrityChecks && resourceIntegrityHashes[filename]) {
|
||||
return resourceIntegrityHashes[filename];
|
||||
|
||||
} else {
|
||||
return "";
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
module.exports = expressApp;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,18 @@ if (!baseUrl.endsWith("/")) {
|
|||
baseUrl += "/";
|
||||
}
|
||||
|
||||
|
||||
let cdnBaseUrl = (process.env.BTCEXP_CDN_BASE_URL || ".").trim();
|
||||
while (cdnBaseUrl.endsWith("/")) {
|
||||
cdnBaseUrl = cdnBaseUrl.substring(0, cdnBaseUrl.length - 1);
|
||||
}
|
||||
|
||||
let s3BucketPath = (process.env.BTCEXP_S3_BUCKET_PATH || "").trim();
|
||||
while (s3BucketPath.endsWith("/")) {
|
||||
s3BucketPath = s3BucketPath.substring(0, s3BucketPath.length - 1);
|
||||
}
|
||||
|
||||
|
||||
const coins = require("./coins.js");
|
||||
const credentials = require("./credentials.js");
|
||||
|
||||
|
|
@ -109,6 +121,13 @@ module.exports = {
|
|||
|
||||
noTxIndexSearchDepth: (+process.env.BTCEXP_NOTXINDEX_SEARCH_DEPTH || 3),
|
||||
|
||||
cdn: {
|
||||
active: (cdnBaseUrl == "." ? false : true),
|
||||
s3Bucket: process.env.BTCEXP_S3_BUCKET,
|
||||
s3BucketPath: s3BucketPath,
|
||||
baseUrl: cdnBaseUrl
|
||||
},
|
||||
|
||||
rpcBlacklist:
|
||||
process.env.BTCEXP_RPC_ALLOWALL.toLowerCase() == "true" ? []
|
||||
: process.env.BTCEXP_RPC_BLACKLIST ? process.env.BTCEXP_RPC_BLACKLIST.split(',').filter(Boolean)
|
||||
|
|
|
|||
|
|
@ -3,16 +3,19 @@ const fs = require("fs");
|
|||
const path = require("path");
|
||||
|
||||
const dirs = [
|
||||
path.join(process.cwd(), "public/js/"),
|
||||
path.join(process.cwd(), "public/style/"),
|
||||
"public/js/",
|
||||
"public/style/",
|
||||
"public/leaflet/",
|
||||
];
|
||||
|
||||
const filetypeMatch = /^.*\.(js|css)$/;
|
||||
|
||||
const hashesByFilename = {};
|
||||
|
||||
dirs.forEach(dirPath => {
|
||||
console.log("\nDirectory: " + dirPath);
|
||||
|
||||
fs.readdirSync(dirPath).forEach(file => {
|
||||
fs.readdirSync(path.join(process.cwd(), dirPath)).forEach(file => {
|
||||
if (file.match(filetypeMatch)) {
|
||||
var content = fs.readFileSync(path.join(dirPath, file));
|
||||
|
||||
|
|
@ -20,9 +23,15 @@ dirs.forEach(dirPath => {
|
|||
|
||||
data = hash.update(content, 'utf-8');
|
||||
|
||||
gen_hash= data.digest('base64');
|
||||
gen_hash = data.digest('base64');
|
||||
|
||||
console.log("File: " + file + " -> " + gen_hash);
|
||||
console.log("\t" + file + " -> " + gen_hash);
|
||||
|
||||
hashesByFilename[file] = `sha384-${gen_hash}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
fs.writeFileSync(path.join(process.cwd(), "public/txt/resource-integrity.json"), JSON.stringify(hashesByFilename, null, 4));
|
||||
|
||||
console.log("\npublic/txt/resource-integrity.json written.\n");
|
||||
12
bin/www
12
bin/www
|
|
@ -13,10 +13,16 @@ debug(`Set max_old_space_size to ${maxOldSpaceSize} MB`);
|
|||
app.set('port', process.env.PORT || process.env.BTCEXP_PORT || 3002);
|
||||
app.set('host', process.env.BTCEXP_HOST || '127.0.0.1');
|
||||
|
||||
const server = app.listen(app.get('port'), app.get('host'), function() {
|
||||
debug('Express server listening on ' + server.address().address + ':' + server.address().port);
|
||||
const server = app.listen(app.get('port'), app.get('host'), () => {
|
||||
debug('Express server starting on ' + server.address().address + ':' + server.address().port);
|
||||
|
||||
if (app.onStartup) {
|
||||
app.onStartup();
|
||||
(async function() {
|
||||
await app.onStartup();
|
||||
|
||||
})();
|
||||
|
||||
}
|
||||
|
||||
debug('Express server startup complete.');
|
||||
});
|
||||
|
|
|
|||
2256
npm-shrinkwrap.json
generated
2256
npm-shrinkwrap.json
generated
File diff suppressed because it is too large
Load diff
|
|
@ -29,7 +29,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"async": "^3.2.2",
|
||||
"axios": "^0.26.1",
|
||||
"axios": "^0.27.2",
|
||||
"basic-auth": "^2.0.1",
|
||||
"bech32": "2.0.0",
|
||||
"bitcoinjs-lib": "^5.2.0",
|
||||
|
|
@ -49,16 +49,17 @@
|
|||
"express": "^4.17.1",
|
||||
"express-async-handler": "^1.1.4",
|
||||
"express-session": "^1.17.2",
|
||||
"@janoside/app-utils": "github:janoside/app-utils#22bfc2fd",
|
||||
"jstransformer-markdown-it": "^2.1.0",
|
||||
"lru-cache": "^6.0.0",
|
||||
"markdown-it": "^12.0.6",
|
||||
"markdown-it": "^13.0.0",
|
||||
"md5": "^2.3.0",
|
||||
"meow": "^9.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"moment-duration-format": "^2.3.2",
|
||||
"morgan": "^1.10.0",
|
||||
"on-headers": "^1.0.2",
|
||||
"pidusage": "^2.0.21",
|
||||
"pidusage": "^3.0.0",
|
||||
"pug": "^3.0.2",
|
||||
"qrcode": "^1.4.4",
|
||||
"redis": "^3.0.2",
|
||||
|
|
|
|||
20
public/txt/resource-integrity.json
Normal file
20
public/txt/resource-integrity.json
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
{
|
||||
"bootstrap.bundle.min.js": "sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p",
|
||||
"chart.min.js": "sha384-ovAl4wbIXnEbY76lSb6GrprFBkoYeu4RKYYNMsADThIn2AweWPOyJqoyR/8/kgML",
|
||||
"chartjs-adapter-moment.min.js": "sha384-Z9r2EsEmivx0l8T8TvYoqqGcpO0cCjKbqVXB8tYUa0hIWKtGVl0TmaF263CjS6XR",
|
||||
"dataTables.bootstrap4.min.js": "sha384-uiSTMvD1kcI19sAHJDVf68medP9HA2E2PzGis9Efmfsdb8p9+mvbQNgFhzii1MEX",
|
||||
"decimal.js": "sha384-AZER8B64Ei3MdcUsKj9o83PHYCWb4dY9wJz58HzSDLat+G/QlGUdXhIlNOH56LUe",
|
||||
"fontawesome.min.js": "sha384-haqrlim99xjfMxRP6EWtafs0sB1WKcMdynwZleuUSwJR0mDeRYbhtY+KPMr+JL6f",
|
||||
"highlight.pack.js": "sha384-OGoFdvlhYqw3L+BFpHxdz5136RO9tUlt7OZ2qQZ0N6Z9Qqx0rCQBsg9ko7X4vC64",
|
||||
"jquery.dataTables.min.js": "sha384-rgWRqC0OFPisxlUvl332tiM/qmaNxnlY46eksSZD84t+s2vZlqGeHrncwIRX7CGp",
|
||||
"jquery.min.js": "sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK",
|
||||
"moment.min.js": "sha384-CJyhAlbbRZX14Q8KxKBt0na1ad4KBs9PklAiNk2Efxs9sgimbIZm9kYLJQeNMUfM",
|
||||
"sentry.min.js": "sha384-da/Bo2Ah6Uw3mlhl6VINMblg2SyGbSnULKrukse3P5D9PTJi4np9HoKvR19D7zOL",
|
||||
"site.js": "sha384-RNCa5sCPh1hkJyKAu8z5Hai8r9IxpVBF2W6GFhSb2JSuSlECW8d85ufzyXBSRwLZ",
|
||||
"dark.css": "sha384-A2fwoNoA4lHJfgim7XUOLsWNI5V2MKx17vQ1Wn0vPdwVjhNZ21jWAh3KYxq2gsKG",
|
||||
"dataTables.bootstrap4.min.css": "sha384-EkHEUZ6lErauT712zSr0DZ2uuCmi3DoQj6ecNdHQXpMpFNGAQ48WjfXCE5n20W+R",
|
||||
"highlight.min.css": "sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ",
|
||||
"light.css": "sha384-pVyltRErny1kThw+BcjtLDkOEpGLSf8/1bsyqimIIA7fuoAobGDW8UfstUY8uWYZ",
|
||||
"leaflet.css": "sha384-6wKUKNzA6h/S6gZ1lWQppeGaVXvK1AUAsEznGBghzlEu1fNcxJGYVRiroSHr+OwU",
|
||||
"leaflet.js": "sha384-RFZC58YeKApoNsIbBxf4z6JJXmh+geBSgkCQXFyh+4tiFSJmJBt+2FbjxW7Ar16M"
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
mixin themeCss
|
||||
link(id="light-theme-link-tag", rel="stylesheet", href=`./style/light.css?v=${cacheId}`, integrity="sha384-pVyltRErny1kThw+BcjtLDkOEpGLSf8/1bsyqimIIA7fuoAobGDW8UfstUY8uWYZ")
|
||||
link(id="dark-theme-link-tag", rel=(userSettings.uiTheme == "dark" ? "stylesheet" : false), href=`./style/dark.css?v=${cacheId}`, integrity="sha384-A2fwoNoA4lHJfgim7XUOLsWNI5V2MKx17vQ1Wn0vPdwVjhNZ21jWAh3KYxq2gsKG")
|
||||
link(id="light-theme-link-tag", rel=(userSettings.uiTheme != "dark" ? "stylesheet" : false), href=assetUrl(`./style/light.css`), integrity=assetIntegrity("light.css"), crossorigin="anonymous")
|
||||
link(id="dark-theme-link-tag", rel=(userSettings.uiTheme == "dark" ? "stylesheet" : false), href=assetUrl(`./style/dark.css`), integrity=assetIntegrity("dark.css"), crossorigin="anonymous")
|
||||
|
||||
|
||||
mixin sharedScriptTags
|
||||
script(src=`./js/jquery.min.js?v=${cacheId}`, integrity="sha384-vtXRMe3mGCbOeY7l30aIg8H9p3GdeSe4IFlP6G8JMa7o7lXvnz3GFKzPxzJdPfGK")
|
||||
script(src=`./js/bootstrap.bundle.min.js?v=${cacheId}`, integrity="sha384-ka7Sk0Gln4gmtz2MlQnikT1wXgYsOg+OMhuP+IlRH9sENBO0LRn5q+8nbTov4+1p")
|
||||
script(defer, src=`./js/fontawesome.min.js?v=${cacheId}`, integrity="sha384-haqrlim99xjfMxRP6EWtafs0sB1WKcMdynwZleuUSwJR0mDeRYbhtY+KPMr+JL6f")
|
||||
script(src=assetUrl(`./js/jquery.min.js`), integrity=assetIntegrity("jquery.min.js"), crossorigin="anonymous")
|
||||
script(src=assetUrl(`./js/bootstrap.bundle.min.js`), integrity=assetIntegrity("bootstrap.bundle.min.js"), crossorigin="anonymous")
|
||||
script(defer, src=assetUrl(`./js/fontawesome.min.js`), integrity=assetIntegrity("fontawesome.min.js"), crossorigin="anonymous")
|
||||
|
||||
script(src=`./js/site.js?v=${cacheId}`, integrity="sha384-RNCa5sCPh1hkJyKAu8z5Hai8r9IxpVBF2W6GFhSb2JSuSlECW8d85ufzyXBSRwLZ")
|
||||
script(src=assetUrl(`./js/site.js`), integrity=assetIntegrity("site.js"), crossorigin="anonymous")
|
||||
|
||||
|
||||
mixin pageTitle(text, subtext, copyableSubtext=false)
|
||||
|
|
@ -836,11 +836,11 @@ mixin pagination(limit, offset, sort, itemCount, baseUrl, justification="start",
|
|||
|
||||
|
||||
mixin graphPageScriptSetup(dateAdapterNeeded=false)
|
||||
script(src=`./js/chart.min.js?v=${cacheId}`, integrity="sha384-ovAl4wbIXnEbY76lSb6GrprFBkoYeu4RKYYNMsADThIn2AweWPOyJqoyR/8/kgML")
|
||||
script(src=assetUrl(`./js/chart.min.js`), integrity=assetIntegrity("chart.min.js"), crossorigin="anonymous")
|
||||
|
||||
if (dateAdapterNeeded)
|
||||
script(src=`./js/moment.min.js?v=${cacheId}`, integrity="sha384-CJyhAlbbRZX14Q8KxKBt0na1ad4KBs9PklAiNk2Efxs9sgimbIZm9kYLJQeNMUfM")
|
||||
script(src=`./js/chartjs-adapter-moment.min.js?v=${cacheId}`, integrity="sha384-Z9r2EsEmivx0l8T8TvYoqqGcpO0cCjKbqVXB8tYUa0hIWKtGVl0TmaF263CjS6XR")
|
||||
script(src=assetUrl(`./js/moment.min.js`), integrity=assetIntegrity("moment.min.js"), crossorigin="anonymous")
|
||||
script(src=assetUrl(`./js/chartjs-adapter-moment.min.js`), integrity=assetIntegrity("chartjs-adapter-moment.min.js"), crossorigin="anonymous")
|
||||
|
||||
if (userSettings.uiTheme && userSettings.uiTheme == "dark")
|
||||
script.
|
||||
|
|
@ -851,7 +851,7 @@ mixin graphPageScriptSetup(dateAdapterNeeded=false)
|
|||
Chart.defaults.color='black';
|
||||
var gridLineColor = "#dddddd";
|
||||
|
||||
script(src='./js/decimal.js')
|
||||
script(src=assetUrl('./js/decimal.js'))
|
||||
|
||||
|
||||
mixin quote(quote, quoteIndex=-1)
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ html(lang="en")
|
|||
meta(name="description" content="Open-source, easy-to-use, educational Bitcoin explorer whose only dependency is your Bitcoin Core node.")
|
||||
|
||||
if (!homepage)
|
||||
link(rel="stylesheet", href=`./style/highlight.min.css?v=${appVersion}`, integrity="sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ")
|
||||
link(rel="stylesheet", href=assetUrl(`./style/highlight.min.css`), integrity=assetIntegrity("highlight.min.css"), crossorigin="anonymous")
|
||||
|
||||
|
||||
+themeCss
|
||||
|
|
@ -32,9 +32,9 @@ html(lang="en")
|
|||
meta(property="twitter:image:src" content=(metaImgUrl != undefined ? metaImgUrl : "https://bitcoinexplorer.org/img/preview.png"))
|
||||
meta(property="twitter:domain" content="bitcoinexplorer.org")
|
||||
|
||||
link(rel="apple-touch-icon", sizes="180x180", href="./img/logo/apple-touch-icon.png")
|
||||
link(rel="icon", type="image/png", sizes="32x32", href="./img/logo/favicon-32x32.png")
|
||||
link(rel="icon", type="image/png", sizes="16x16", href="./img/logo/favicon-16x16.png")
|
||||
link(rel="apple-touch-icon", sizes="180x180", href=assetUrl("./img/logo/apple-touch-icon.png"))
|
||||
link(rel="icon", type="image/png", sizes="32x32", href=assetUrl("./img/logo/favicon-32x32.png"))
|
||||
link(rel="icon", type="image/png", sizes="16x16", href=assetUrl("./img/logo/favicon-16x16.png"))
|
||||
link(rel="manifest", href="./img/logo/site.webmanifest")
|
||||
link(rel="mask-icon", href="./img/logo/safari-pinned-tab.svg", color="#f7931a")
|
||||
link(rel="shortcut icon", href="./img/logo/favicon.ico")
|
||||
|
|
@ -78,7 +78,7 @@ html(lang="en")
|
|||
});
|
||||
|
||||
if (!homepage)
|
||||
script(src="./js/highlight.pack.js", integrity="sha384-OGoFdvlhYqw3L+BFpHxdz5136RO9tUlt7OZ2qQZ0N6Z9Qqx0rCQBsg9ko7X4vC64")
|
||||
script(src=assetUrl(`./js/highlight.pack.js`), integrity=assetIntegrity("highlight.pack.js"), crossorigin="anonymous")
|
||||
script.
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
hljs.highlightAll();
|
||||
|
|
@ -86,7 +86,7 @@ html(lang="en")
|
|||
|
||||
|
||||
if (config.credentials.sentryUrl && config.credentials.sentryUrl.length > 0)
|
||||
script(src="./js/sentry.min.js", integrity="sha384-da/Bo2Ah6Uw3mlhl6VINMblg2SyGbSnULKrukse3P5D9PTJi4np9HoKvR19D7zOL", crossorigin="anonymous")
|
||||
script(src=assetUrl("./js/sentry.min.js"), integrity=assetIntegrity("sentry.min.js"), crossorigin="anonymous")
|
||||
script.
|
||||
Sentry.init({ dsn: '#{config.credentials.sentryUrl}' });
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ html(lang="en")
|
|||
meta(name="description" content="Open-source, easy-to-use, educational Bitcoin explorer whose only dependency is your Bitcoin Core node.")
|
||||
|
||||
if (!homepage)
|
||||
link(rel="stylesheet", href=`./style/highlight.min.css?v=${appVersion}`, integrity="sha384-s4RLYRjGGbVqKOyMGGwfxUTMOO6D7r2eom7hWZQ6BjK2Df4ZyfzLXEkonSm0KLIQ")
|
||||
link(rel="stylesheet", href=assetUrl(`./style/highlight.min.css`), integrity=assetIntegrity("highlight.min.css"), crossorigin="anonymous")
|
||||
|
||||
|
||||
+themeCss
|
||||
|
|
@ -35,12 +35,12 @@ html(lang="en")
|
|||
if (!network.endsWith("net") && network != "regtest")
|
||||
- network = (network + "net"); // handles "main" -> "mainnet" and "test" -> "testnet"
|
||||
|
||||
link(rel="apple-touch-icon", sizes="180x180", href=`./img/logo/${network}/apple-touch-icon.png`)
|
||||
link(rel="icon", type="image/png", sizes="32x32", href=`./img/logo/${network}/favicon-32x32.png`)
|
||||
link(rel="icon", type="image/png", sizes="16x16", href=`./img/logo/${network}/favicon-16x16.png`)
|
||||
link(rel="apple-touch-icon", sizes="180x180", href=assetUrl(`./img/logo/${network}/apple-touch-icon.png`))
|
||||
link(rel="icon", type="image/png", sizes="32x32", href=assetUrl(`./img/logo/${network}/favicon-32x32.png`))
|
||||
link(rel="icon", type="image/png", sizes="16x16", href=assetUrl(`./img/logo/${network}/favicon-16x16.png`))
|
||||
link(rel="manifest", href=`./img/logo/${network}/site.webmanifest`)
|
||||
link(rel="mask-icon", href=`./img/logo/${network}/safari-pinned-tab.svg`, color="#f7931a")
|
||||
link(rel="shortcut icon", href=`./img/logo/${network}/favicon.ico`)
|
||||
link(rel="shortcut icon", href=assetUrl(`./img/logo/${network}/favicon.ico`))
|
||||
meta(name="apple-mobile-web-app-title", content="BTC Explorer")
|
||||
meta(name="application-name", content="BTC Explorer")
|
||||
meta(name="msapplication-TileColor", content="#022e70")
|
||||
|
|
@ -58,9 +58,9 @@ html(lang="en")
|
|||
a.navbar-brand(href="./")
|
||||
span
|
||||
if (coinConfig.logoUrlsByNetwork && coinConfig.logoUrlsByNetwork[activeBlockchain])
|
||||
img.header-image(src=coinConfig.logoUrlsByNetwork[activeBlockchain], alt="logo")
|
||||
img.header-image(src=assetUrl(coinConfig.logoUrlsByNetwork[activeBlockchain]), alt="logo")
|
||||
else
|
||||
img.header-image(src="./img/logo/mainnet/logo.svg", alt="logo")
|
||||
img.header-image(src=assetUrl("./img/logo/mainnet/logo.svg"), alt="logo")
|
||||
|
||||
if (coinConfig.siteTitlesByNetwork && coinConfig.siteTitlesByNetwork[activeBlockchain])
|
||||
span.fw-light #{coinConfig.siteTitlesByNetwork[activeBlockchain]}
|
||||
|
|
@ -313,7 +313,7 @@ html(lang="en")
|
|||
|
||||
each demoSite in demoSites
|
||||
a.me-2(href=coinConfig.demoSiteUrlsByNetwork[demoSite], title=coinConfig.siteTitlesByNetwork[demoSite], data-bs-toggle="tooltip")
|
||||
img(src=coinConfig.logoUrlsByNetwork[demoSite], alt=coinConfig.siteTitlesByNetwork[demoSite], style="width: 40px; height: 40px;")
|
||||
img(src=assetUrl(coinConfig.logoUrlsByNetwork[demoSite]), alt=coinConfig.siteTitlesByNetwork[demoSite], style="width: 40px; height: 40px;")
|
||||
|
||||
.col-lg-3
|
||||
.mb-4
|
||||
|
|
@ -386,7 +386,7 @@ html(lang="en")
|
|||
console.log(`Performance ID: ${perfId}`);
|
||||
|
||||
if (!homepage)
|
||||
script(src="./js/highlight.pack.js", integrity="sha384-OGoFdvlhYqw3L+BFpHxdz5136RO9tUlt7OZ2qQZ0N6Z9Qqx0rCQBsg9ko7X4vC64")
|
||||
script(src=assetUrl(`./js/highlight.pack.js`), integrity=assetIntegrity("highlight.pack.js"), crossorigin="anonymous")
|
||||
script.
|
||||
document.addEventListener("DOMContentLoaded", function(event) {
|
||||
hljs.highlightAll();
|
||||
|
|
@ -398,7 +398,7 @@ html(lang="en")
|
|||
|
||||
|
||||
if (config.credentials.sentryUrl && config.credentials.sentryUrl.length > 0)
|
||||
script(src="./js/sentry.min.js", integrity="sha384-da/Bo2Ah6Uw3mlhl6VINMblg2SyGbSnULKrukse3P5D9PTJi4np9HoKvR19D7zOL", crossorigin="anonymous")
|
||||
script(src=assetUrl("./js/sentry.min.js"), integrity=assetIntegrity("sentry.min.js"), crossorigin="anonymous")
|
||||
script.
|
||||
Sentry.init({ dsn: '#{config.credentials.sentryUrl}' });
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ extends layout
|
|||
block headContent
|
||||
title Peers
|
||||
|
||||
link(rel="stylesheet", href="./style/dataTables.bootstrap4.min.css", integrity="sha384-EkHEUZ6lErauT712zSr0DZ2uuCmi3DoQj6ecNdHQXpMpFNGAQ48WjfXCE5n20W+R")
|
||||
link(rel="stylesheet", href=assetUrl("./style/dataTables.bootstrap4.min.css"), integrity=assetIntegrity("dataTables.bootstrap4.min.css"), crossorigin="anonymous")
|
||||
|
||||
link(rel="stylesheet", href="./leaflet/leaflet.css", integrity="sha384-6wKUKNzA6h/S6gZ1lWQppeGaVXvK1AUAsEznGBghzlEu1fNcxJGYVRiroSHr+OwU")
|
||||
link(rel="stylesheet", href=assetUrl("./leaflet/leaflet.css"), integrity=assetIntegrity("leaflet.css"), crossorigin="anonymous")
|
||||
|
||||
script(src="./leaflet/leaflet.js", integrity="sha384-RFZC58YeKApoNsIbBxf4z6JJXmh+geBSgkCQXFyh+4tiFSJmJBt+2FbjxW7Ar16M")
|
||||
script(src=assetUrl("./leaflet/leaflet.js"), integrity=assetIntegrity("leaflet.js"), crossorigin="anonymous")
|
||||
|
||||
style.
|
||||
.versions-hidden-rows, .services-hidden-rows {
|
||||
|
|
@ -193,8 +193,8 @@ block content
|
|||
|
||||
|
||||
block endOfBody
|
||||
script(src="./js/jquery.dataTables.min.js", integrity="sha384-rgWRqC0OFPisxlUvl332tiM/qmaNxnlY46eksSZD84t+s2vZlqGeHrncwIRX7CGp")
|
||||
script(src="./js/dataTables.bootstrap4.min.js", integrity="sha384-uiSTMvD1kcI19sAHJDVf68medP9HA2E2PzGis9Efmfsdb8p9+mvbQNgFhzii1MEX")
|
||||
script(src=assetUrl("./js/jquery.dataTables.min.js"), integrity=assetIntegrity("jquery.dataTables.min.js"), crossorigin="anonymous")
|
||||
script(src=assetUrl("./js/dataTables.bootstrap4.min.js"), integrity=assetIntegrity("dataTables.bootstrap4.min.js"), crossorigin="anonymous")
|
||||
|
||||
script.
|
||||
$(document).ready(function() {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue