Merge branch 'master' into mononaut/use-acceleration-websocket

This commit is contained in:
softsimon 2024-10-07 15:58:13 +09:00 committed by GitHub
commit e6980a832b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
39 changed files with 1007 additions and 666 deletions

View File

@ -28,7 +28,7 @@
"POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json", "POOLS_JSON_URL": "https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json",
"POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master", "POOLS_JSON_TREE_URL": "https://api.github.com/repos/mempool/mining-pools/git/trees/master",
"AUDIT": false, "AUDIT": false,
"RUST_GBT": false, "RUST_GBT": true,
"LIMIT_GBT": false, "LIMIT_GBT": false,
"CPFP_INDEXING": false, "CPFP_INDEXING": false,
"DISK_CACHE_BLOCK_INTERVAL": 6, "DISK_CACHE_BLOCK_INTERVAL": 6,

View File

@ -42,7 +42,7 @@ describe('Mempool Backend Config', () => {
POOLS_JSON_TREE_URL: 'https://api.github.com/repos/mempool/mining-pools/git/trees/master', POOLS_JSON_TREE_URL: 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json', POOLS_JSON_URL: 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json',
AUDIT: false, AUDIT: false,
RUST_GBT: false, RUST_GBT: true,
LIMIT_GBT: false, LIMIT_GBT: false,
CPFP_INDEXING: false, CPFP_INDEXING: false,
MAX_BLOCKS_BULK_QUERY: 0, MAX_BLOCKS_BULK_QUERY: 0,

View File

@ -34,6 +34,7 @@ import { calculateFastBlockCpfp, calculateGoodBlockCpfp } from './cpfp';
import mempool from './mempool'; import mempool from './mempool';
import CpfpRepository from '../repositories/CpfpRepository'; import CpfpRepository from '../repositories/CpfpRepository';
import accelerationApi from './services/acceleration'; import accelerationApi from './services/acceleration';
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
class Blocks { class Blocks {
private blocks: BlockExtended[] = []; private blocks: BlockExtended[] = [];
@ -342,7 +343,12 @@ class Blocks {
id: pool.uniqueId, id: pool.uniqueId,
name: pool.name, name: pool.name,
slug: pool.slug, slug: pool.slug,
minerNames: null,
}; };
if (extras.pool.name === 'OCEAN') {
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
}
} }
extras.matchRate = null; extras.matchRate = null;

View File

@ -121,6 +121,7 @@ class TransactionUtils {
const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor const adjustedVsize = Math.max(fractionalVsize, sigops * 5); // adjusted vsize = Max(weight, sigops * bytes_per_sigop) / witness_scale_factor
const feePerVbytes = (transaction.fee || 0) / fractionalVsize; const feePerVbytes = (transaction.fee || 0) / fractionalVsize;
const adjustedFeePerVsize = (transaction.fee || 0) / adjustedVsize; const adjustedFeePerVsize = (transaction.fee || 0) / adjustedVsize;
const effectiveFeePerVsize = transaction['effectiveFeePerVsize'] || adjustedFeePerVsize || feePerVbytes;
const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, { const transactionExtended: MempoolTransactionExtended = Object.assign(transaction, {
order: this.txidToOrdering(transaction.txid), order: this.txidToOrdering(transaction.txid),
vsize, vsize,
@ -128,7 +129,7 @@ class TransactionUtils {
sigops, sigops,
feePerVsize: feePerVbytes, feePerVsize: feePerVbytes,
adjustedFeePerVsize: adjustedFeePerVsize, adjustedFeePerVsize: adjustedFeePerVsize,
effectiveFeePerVsize: adjustedFeePerVsize, effectiveFeePerVsize: effectiveFeePerVsize,
}); });
if (!transactionExtended?.status?.confirmed && !transactionExtended.firstSeen) { if (!transactionExtended?.status?.confirmed && !transactionExtended.firstSeen) {
transactionExtended.firstSeen = Math.round((Date.now() / 1000)); transactionExtended.firstSeen = Math.round((Date.now() / 1000));

View File

@ -193,7 +193,7 @@ const defaults: IConfig = {
'POOLS_JSON_URL': 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json', 'POOLS_JSON_URL': 'https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json',
'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master', 'POOLS_JSON_TREE_URL': 'https://api.github.com/repos/mempool/mining-pools/git/trees/master',
'AUDIT': false, 'AUDIT': false,
'RUST_GBT': false, 'RUST_GBT': true,
'LIMIT_GBT': false, 'LIMIT_GBT': false,
'CPFP_INDEXING': false, 'CPFP_INDEXING': false,
'MAX_BLOCKS_BULK_QUERY': 0, 'MAX_BLOCKS_BULK_QUERY': 0,

View File

@ -299,6 +299,7 @@ export interface BlockExtension {
id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id` id: number; // Note - This is the `unique_id`, not to mix with the auto increment `id`
name: string; name: string;
slug: string; slug: string;
minerNames: string[] | null;
}; };
avgFee: number; avgFee: number;
avgFeeRate: number; avgFeeRate: number;

View File

@ -14,6 +14,7 @@ import chainTips from '../api/chain-tips';
import blocks from '../api/blocks'; import blocks from '../api/blocks';
import BlocksAuditsRepository from './BlocksAuditsRepository'; import BlocksAuditsRepository from './BlocksAuditsRepository';
import transactionUtils from '../api/transaction-utils'; import transactionUtils from '../api/transaction-utils';
import { parseDATUMTemplateCreator } from '../utils/bitcoin-script';
interface DatabaseBlock { interface DatabaseBlock {
id: string; id: string;
@ -1054,6 +1055,7 @@ class BlocksRepository {
id: dbBlk.poolId, id: dbBlk.poolId,
name: dbBlk.poolName, name: dbBlk.poolName,
slug: dbBlk.poolSlug, slug: dbBlk.poolSlug,
minerNames: null,
}; };
extras.avgFee = dbBlk.avgFee; extras.avgFee = dbBlk.avgFee;
extras.avgFeeRate = dbBlk.avgFeeRate; extras.avgFeeRate = dbBlk.avgFeeRate;
@ -1123,6 +1125,10 @@ class BlocksRepository {
} }
} }
if (extras.pool.name === 'OCEAN') {
extras.pool.minerNames = parseDATUMTemplateCreator(extras.coinbaseRaw);
}
blk.extras = <BlockExtension>extras; blk.extras = <BlockExtension>extras;
return <BlockExtended>blk; return <BlockExtended>blk;
} }

View File

@ -201,3 +201,27 @@ export function getVarIntLength(n: number): number {
return 9; return 9;
} }
} }
/** Extracts miner names from a DATUM coinbase transaction */
export function parseDATUMTemplateCreator(coinbaseRaw: string): string[] | null {
let bytes: number[] = [];
for (let c = 0; c < coinbaseRaw.length; c += 2) {
bytes.push(parseInt(coinbaseRaw.slice(c, c + 2), 16));
}
// Skip block height
let tagLengthByte = 1 + bytes[0];
let tagsLength = bytes[tagLengthByte];
if (tagsLength == 0x4c) {
tagLengthByte += 1;
tagsLength = bytes[tagLengthByte];
}
const tagStart = tagLengthByte + 1;
const tags = bytes.slice(tagStart, tagStart + tagsLength);
let tagString = String.fromCharCode(...tags);
tagString = tagString.replace('\x00', '');
return tagString.split('\x0f').map((name) => name.replace(/[^a-zA-Z0-9 ]/g, ''));
}

View File

@ -30,7 +30,7 @@ __MEMPOOL_AUTOMATIC_POOLS_UPDATE__=${MEMPOOL_AUTOMATIC_POOLS_UPDATE:=false}
__MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json} __MEMPOOL_POOLS_JSON_URL__=${MEMPOOL_POOLS_JSON_URL:=https://raw.githubusercontent.com/mempool/mining-pools/master/pools-v2.json}
__MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master} __MEMPOOL_POOLS_JSON_TREE_URL__=${MEMPOOL_POOLS_JSON_TREE_URL:=https://api.github.com/repos/mempool/mining-pools/git/trees/master}
__MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false} __MEMPOOL_AUDIT__=${MEMPOOL_AUDIT:=false}
__MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=false} __MEMPOOL_RUST_GBT__=${MEMPOOL_RUST_GBT:=true}
__MEMPOOL_LIMIT_GBT__=${MEMPOOL_LIMIT_GBT:=false} __MEMPOOL_LIMIT_GBT__=${MEMPOOL_LIMIT_GBT:=false}
__MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false} __MEMPOOL_CPFP_INDEXING__=${MEMPOOL_CPFP_INDEXING:=false}
__MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0} __MEMPOOL_MAX_BLOCKS_BULK_QUERY__=${MEMPOOL_MAX_BLOCKS_BULK_QUERY:=0}

View File

@ -62,7 +62,7 @@
"optionalDependencies": { "optionalDependencies": {
"@cypress/schematic": "^2.5.0", "@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3", "@types/cypress": "^1.1.3",
"cypress": "^13.14.0", "cypress": "^13.15.0",
"cypress-fail-on-console-error": "~5.1.0", "cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1", "cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1", "mock-socket": "~9.3.1",
@ -3113,9 +3113,9 @@
} }
}, },
"node_modules/@cypress/request": { "node_modules/@cypress/request": {
"version": "3.0.1", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
"integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"aws-sign2": "~0.7.0", "aws-sign2": "~0.7.0",
@ -3124,14 +3124,14 @@
"combined-stream": "~1.0.6", "combined-stream": "~1.0.6",
"extend": "~3.0.2", "extend": "~3.0.2",
"forever-agent": "~0.6.1", "forever-agent": "~0.6.1",
"form-data": "~2.3.2", "form-data": "~4.0.0",
"http-signature": "~1.3.6", "http-signature": "~1.4.0",
"is-typedarray": "~1.0.0", "is-typedarray": "~1.0.0",
"isstream": "~0.1.2", "isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1", "json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19", "mime-types": "~2.1.19",
"performance-now": "^2.1.0", "performance-now": "^2.1.0",
"qs": "6.10.4", "qs": "6.13.0",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"tough-cookie": "^4.1.3", "tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0", "tunnel-agent": "^0.6.0",
@ -4313,9 +4313,9 @@
} }
}, },
"node_modules/@rollup/rollup-android-arm-eabi": { "node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4325,9 +4325,9 @@
] ]
}, },
"node_modules/@rollup/rollup-android-arm64": { "node_modules/@rollup/rollup-android-arm64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4337,9 +4337,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-arm64": { "node_modules/@rollup/rollup-darwin-arm64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4349,9 +4349,9 @@
] ]
}, },
"node_modules/@rollup/rollup-darwin-x64": { "node_modules/@rollup/rollup-darwin-x64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4361,9 +4361,21 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm-gnueabihf": { "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
"cpu": [
"arm"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-arm-musleabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
@ -4373,9 +4385,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-gnu": { "node_modules/@rollup/rollup-linux-arm64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4385,9 +4397,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-arm64-musl": { "node_modules/@rollup/rollup-linux-arm64-musl": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4396,10 +4408,22 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
"cpu": [
"ppc64"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-riscv64-gnu": { "node_modules/@rollup/rollup-linux-riscv64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
"cpu": [ "cpu": [
"riscv64" "riscv64"
], ],
@ -4408,10 +4432,22 @@
"linux" "linux"
] ]
}, },
"node_modules/@rollup/rollup-linux-s390x-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
"cpu": [
"s390x"
],
"optional": true,
"os": [
"linux"
]
},
"node_modules/@rollup/rollup-linux-x64-gnu": { "node_modules/@rollup/rollup-linux-x64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4421,9 +4457,9 @@
] ]
}, },
"node_modules/@rollup/rollup-linux-x64-musl": { "node_modules/@rollup/rollup-linux-x64-musl": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4433,9 +4469,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-arm64-msvc": { "node_modules/@rollup/rollup-win32-arm64-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
@ -4445,9 +4481,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-ia32-msvc": { "node_modules/@rollup/rollup-win32-ia32-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
@ -4457,9 +4493,9 @@
] ]
}, },
"node_modules/@rollup/rollup-win32-x64-msvc": { "node_modules/@rollup/rollup-win32-x64-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
@ -4801,9 +4837,9 @@
} }
}, },
"node_modules/@types/estree": { "node_modules/@types/estree": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
}, },
"node_modules/@types/express": { "node_modules/@types/express": {
"version": "4.17.13", "version": "4.17.13",
@ -5797,9 +5833,9 @@
} }
}, },
"node_modules/aws4": { "node_modules/aws4": {
"version": "1.12.0", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"optional": true "optional": true
}, },
"node_modules/axios": { "node_modules/axios": {
@ -6065,20 +6101,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/body-parser/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/bonjour-service": { "node_modules/bonjour-service": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz", "resolved": "https://registry.npmjs.org/bonjour-service/-/bonjour-service-1.2.1.tgz",
@ -8045,13 +8067,13 @@
"peer": true "peer": true
}, },
"node_modules/cypress": { "node_modules/cypress": {
"version": "13.14.0", "version": "13.15.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.0.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
"integrity": "sha512-r0+nhd033x883YL6068futewUsl02Q7rWiinyAAIBDW/OOTn+UMILWgNuCiY3vtJjd53efOqq5R9dctQk/rKiw==", "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
"hasInstallScript": true, "hasInstallScript": true,
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"@cypress/request": "^3.0.1", "@cypress/request": "^3.0.4",
"@cypress/xvfb": "^1.2.4", "@cypress/xvfb": "^1.2.4",
"@types/sinonjs__fake-timers": "8.1.1", "@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2", "@types/sizzle": "^2.3.2",
@ -9896,20 +9918,6 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/express/node_modules/qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"dependencies": {
"side-channel": "^1.0.6"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/express/node_modules/safe-buffer": { "node_modules/express/node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -10305,17 +10313,17 @@
} }
}, },
"node_modules/form-data": { "node_modules/form-data": {
"version": "2.3.3", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.6", "combined-stream": "^1.0.8",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
}, },
"engines": { "engines": {
"node": ">= 0.12" "node": ">= 6"
} }
}, },
"node_modules/forwarded": { "node_modules/forwarded": {
@ -10957,14 +10965,14 @@
} }
}, },
"node_modules/http-signature": { "node_modules/http-signature": {
"version": "1.3.6", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
"integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"assert-plus": "^1.0.0", "assert-plus": "^1.0.0",
"jsprim": "^2.0.2", "jsprim": "^2.0.2",
"sshpk": "^1.14.1" "sshpk": "^1.18.0"
}, },
"engines": { "engines": {
"node": ">=0.10" "node": ">=0.10"
@ -14737,12 +14745,11 @@
} }
}, },
"node_modules/qs": { "node_modules/qs": {
"version": "6.10.4", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"optional": true,
"dependencies": { "dependencies": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
}, },
"engines": { "engines": {
"node": ">=0.6" "node": ">=0.6"
@ -15198,11 +15205,11 @@
} }
}, },
"node_modules/rollup": { "node_modules/rollup": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"dependencies": { "dependencies": {
"@types/estree": "1.0.5" "@types/estree": "1.0.6"
}, },
"bin": { "bin": {
"rollup": "dist/bin/rollup" "rollup": "dist/bin/rollup"
@ -15212,19 +15219,22 @@
"npm": ">=8.0.0" "npm": ">=8.0.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"@rollup/rollup-android-arm-eabi": "4.13.0", "@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.13.0", "@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.13.0", "@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.13.0", "@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0", "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0", "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.13.0", "@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.13.0", "@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.13.0", "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.13.0", "@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.13.0", "@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.13.0", "@rollup/rollup-linux-x64-musl": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -16129,9 +16139,9 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
}, },
"node_modules/sshpk": { "node_modules/sshpk": {
"version": "1.17.0", "version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"asn1": "~0.2.3", "asn1": "~0.2.3",
@ -16725,9 +16735,9 @@
} }
}, },
"node_modules/tough-cookie": { "node_modules/tough-cookie": {
"version": "4.1.3", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"psl": "^1.1.33", "psl": "^1.1.33",
@ -17799,20 +17809,6 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"node_modules/wait-on/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"optional": true,
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/wait-on/node_modules/proxy-from-env": { "node_modules/wait-on/node_modules/proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
@ -20466,9 +20462,9 @@
} }
}, },
"@cypress/request": { "@cypress/request": {
"version": "3.0.1", "version": "3.0.5",
"resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.1.tgz", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.5.tgz",
"integrity": "sha512-TWivJlJi8ZDx2wGOw1dbLuHJKUYX7bWySw377nlnGOW3hP9/MUKIsEdXT/YngWxVdgNCHRBmFlBipE+5/2ZZlQ==", "integrity": "sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==",
"optional": true, "optional": true,
"requires": { "requires": {
"aws-sign2": "~0.7.0", "aws-sign2": "~0.7.0",
@ -20477,14 +20473,14 @@
"combined-stream": "~1.0.6", "combined-stream": "~1.0.6",
"extend": "~3.0.2", "extend": "~3.0.2",
"forever-agent": "~0.6.1", "forever-agent": "~0.6.1",
"form-data": "~2.3.2", "form-data": "~4.0.0",
"http-signature": "~1.3.6", "http-signature": "~1.4.0",
"is-typedarray": "~1.0.0", "is-typedarray": "~1.0.0",
"isstream": "~0.1.2", "isstream": "~0.1.2",
"json-stringify-safe": "~5.0.1", "json-stringify-safe": "~5.0.1",
"mime-types": "~2.1.19", "mime-types": "~2.1.19",
"performance-now": "^2.1.0", "performance-now": "^2.1.0",
"qs": "6.10.4", "qs": "6.13.0",
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"tough-cookie": "^4.1.3", "tough-cookie": "^4.1.3",
"tunnel-agent": "^0.6.0", "tunnel-agent": "^0.6.0",
@ -21229,81 +21225,99 @@
"peer": true "peer": true
}, },
"@rollup/rollup-android-arm-eabi": { "@rollup/rollup-android-arm-eabi": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.24.0.tgz",
"integrity": "sha512-5ZYPOuaAqEH/W3gYsRkxQATBW3Ii1MfaT4EQstTnLKViLi2gLSQmlmtTpGucNP3sXEpOiI5tdGhjdE111ekyEg==", "integrity": "sha512-Q6HJd7Y6xdB48x8ZNVDOqsbh2uByBhgK8PiQgPhwkIw/HC/YX5Ghq2mQY5sRMZWHb3VsFkWooUVOZHKr7DmDIA==",
"optional": true "optional": true
}, },
"@rollup/rollup-android-arm64": { "@rollup/rollup-android-arm64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.24.0.tgz",
"integrity": "sha512-BSbaCmn8ZadK3UAQdlauSvtaJjhlDEjS5hEVVIN3A4bbl3X+otyf/kOJV08bYiRxfejP3DXFzO2jz3G20107+Q==", "integrity": "sha512-ijLnS1qFId8xhKjT81uBHuuJp2lU4x2yxa4ctFPtG+MqEE6+C5f/+X/bStmxapgmwLwiL3ih122xv8kVARNAZA==",
"optional": true "optional": true
}, },
"@rollup/rollup-darwin-arm64": { "@rollup/rollup-darwin-arm64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.24.0.tgz",
"integrity": "sha512-Ovf2evVaP6sW5Ut0GHyUSOqA6tVKfrTHddtmxGQc1CTQa1Cw3/KMCDEEICZBbyppcwnhMwcDce9ZRxdWRpVd6g==", "integrity": "sha512-bIv+X9xeSs1XCk6DVvkO+S/z8/2AMt/2lMqdQbMrmVpgFvXlmde9mLcbQpztXm1tajC3raFDqegsH18HQPMYtA==",
"optional": true "optional": true
}, },
"@rollup/rollup-darwin-x64": { "@rollup/rollup-darwin-x64": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.24.0.tgz",
"integrity": "sha512-U+Jcxm89UTK592vZ2J9st9ajRv/hrwHdnvyuJpa5A2ngGSVHypigidkQJP+YiGL6JODiUeMzkqQzbCG3At81Gg==", "integrity": "sha512-X6/nOwoFN7RT2svEQWUsW/5C/fYMBe4fnLK9DQk4SX4mgVBiTA9h64kjUYPvGQ0F/9xwJ5U5UfTbl6BEjaQdBQ==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-arm-gnueabihf": { "@rollup/rollup-linux-arm-gnueabihf": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.24.0.tgz",
"integrity": "sha512-8wZidaUJUTIR5T4vRS22VkSMOVooG0F4N+JSwQXWSRiC6yfEsFMLTYRFHvby5mFFuExHa/yAp9juSphQQJAijQ==", "integrity": "sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==",
"optional": true
},
"@rollup/rollup-linux-arm-musleabihf": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.24.0.tgz",
"integrity": "sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-arm64-gnu": { "@rollup/rollup-linux-arm64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.24.0.tgz",
"integrity": "sha512-Iu0Kno1vrD7zHQDxOmvweqLkAzjxEVqNhUIXBsZ8hu8Oak7/5VTPrxOEZXYC1nmrBVJp0ZcL2E7lSuuOVaE3+w==", "integrity": "sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-arm64-musl": { "@rollup/rollup-linux-arm64-musl": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.24.0.tgz",
"integrity": "sha512-C31QrW47llgVyrRjIwiOwsHFcaIwmkKi3PCroQY5aVq4H0A5v/vVVAtFsI1nfBngtoRpeREvZOkIhmRwUKkAdw==", "integrity": "sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==",
"optional": true
},
"@rollup/rollup-linux-powerpc64le-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.24.0.tgz",
"integrity": "sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-riscv64-gnu": { "@rollup/rollup-linux-riscv64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.24.0.tgz",
"integrity": "sha512-Oq90dtMHvthFOPMl7pt7KmxzX7E71AfyIhh+cPhLY9oko97Zf2C9tt/XJD4RgxhaGeAraAXDtqxvKE1y/j35lA==", "integrity": "sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==",
"optional": true
},
"@rollup/rollup-linux-s390x-gnu": {
"version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.24.0.tgz",
"integrity": "sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-x64-gnu": { "@rollup/rollup-linux-x64-gnu": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.24.0.tgz",
"integrity": "sha512-yUD/8wMffnTKuiIsl6xU+4IA8UNhQ/f1sAnQebmE/lyQ8abjsVyDkyRkWop0kdMhKMprpNIhPmYlCxgHrPoXoA==", "integrity": "sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==",
"optional": true "optional": true
}, },
"@rollup/rollup-linux-x64-musl": { "@rollup/rollup-linux-x64-musl": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.24.0.tgz",
"integrity": "sha512-9RyNqoFNdF0vu/qqX63fKotBh43fJQeYC98hCaf89DYQpv+xu0D8QFSOS0biA7cGuqJFOc1bJ+m2rhhsKcw1hw==", "integrity": "sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==",
"optional": true "optional": true
}, },
"@rollup/rollup-win32-arm64-msvc": { "@rollup/rollup-win32-arm64-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.24.0.tgz",
"integrity": "sha512-46ue8ymtm/5PUU6pCvjlic0z82qWkxv54GTJZgHrQUuZnVH+tvvSP0LsozIDsCBFO4VjJ13N68wqrKSeScUKdA==", "integrity": "sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==",
"optional": true "optional": true
}, },
"@rollup/rollup-win32-ia32-msvc": { "@rollup/rollup-win32-ia32-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.24.0.tgz",
"integrity": "sha512-P5/MqLdLSlqxbeuJ3YDeX37srC8mCflSyTrUsgbU1c/U9j6l2g2GiIdYaGD9QjdMQPMSgYm7hgg0551wHyIluw==", "integrity": "sha512-xrNcGDU0OxVcPTH/8n/ShH4UevZxKIO6HJFK0e15XItZP2UcaiLFd5kiX7hJnqCbSztUF8Qot+JWBC/QXRPYWQ==",
"optional": true "optional": true
}, },
"@rollup/rollup-win32-x64-msvc": { "@rollup/rollup-win32-x64-msvc": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.13.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.24.0.tgz",
"integrity": "sha512-UKXUQNbO3DOhzLRwHSpa0HnhhCgNODvfoPWv2FCXme8N/ANFfhIPMGuOT+QuKd16+B5yxZ0HdpNlqPvTMS1qfw==", "integrity": "sha512-fbMkAF7fufku0N2dE5TBXcNlg0pt0cJue4xBRE2Qc5Vqikxr4VCgKj/ht6SMdFcOacVA9rqF70APJ8RN/4vMJw==",
"optional": true "optional": true
}, },
"@schematics/angular": { "@schematics/angular": {
@ -21607,9 +21621,9 @@
} }
}, },
"@types/estree": { "@types/estree": {
"version": "1.0.5", "version": "1.0.6",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz",
"integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==" "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw=="
}, },
"@types/express": { "@types/express": {
"version": "4.17.13", "version": "4.17.13",
@ -22369,9 +22383,9 @@
"optional": true "optional": true
}, },
"aws4": { "aws4": {
"version": "1.12.0", "version": "1.13.2",
"resolved": "https://registry.npmjs.org/aws4/-/aws4-1.12.0.tgz", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.13.2.tgz",
"integrity": "sha512-NmWvPnx0F1SfrQbYwOi7OeaNGokp9XhzNioJ/CSBs8Qa4vxug81mhJEAVZwxXuBmYB5KDRfMq/F3RR0BIU7sWg==", "integrity": "sha512-lHe62zvbTB5eEABUVi/AwVh0ZKY9rMMDhmm+eeyuuUQbQ3+J+fONVQOZyj+DdrvD4BY33uYniyRJ4UJIaSKAfw==",
"optional": true "optional": true
}, },
"axios": { "axios": {
@ -22583,14 +22597,6 @@
"requires": { "requires": {
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
},
"qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"requires": {
"side-channel": "^1.0.6"
}
} }
} }
}, },
@ -24100,12 +24106,12 @@
"peer": true "peer": true
}, },
"cypress": { "cypress": {
"version": "13.14.0", "version": "13.15.0",
"resolved": "https://registry.npmjs.org/cypress/-/cypress-13.14.0.tgz", "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.15.0.tgz",
"integrity": "sha512-r0+nhd033x883YL6068futewUsl02Q7rWiinyAAIBDW/OOTn+UMILWgNuCiY3vtJjd53efOqq5R9dctQk/rKiw==", "integrity": "sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==",
"optional": true, "optional": true,
"requires": { "requires": {
"@cypress/request": "^3.0.1", "@cypress/request": "^3.0.4",
"@cypress/xvfb": "^1.2.4", "@cypress/xvfb": "^1.2.4",
"@types/sinonjs__fake-timers": "8.1.1", "@types/sinonjs__fake-timers": "8.1.1",
"@types/sizzle": "^2.3.2", "@types/sizzle": "^2.3.2",
@ -25554,14 +25560,6 @@
"ee-first": "1.1.1" "ee-first": "1.1.1"
} }
}, },
"qs": {
"version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"requires": {
"side-channel": "^1.0.6"
}
},
"safe-buffer": { "safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -25853,13 +25851,13 @@
"optional": true "optional": true
}, },
"form-data": { "form-data": {
"version": "2.3.3", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"optional": true, "optional": true,
"requires": { "requires": {
"asynckit": "^0.4.0", "asynckit": "^0.4.0",
"combined-stream": "^1.0.6", "combined-stream": "^1.0.8",
"mime-types": "^2.1.12" "mime-types": "^2.1.12"
} }
}, },
@ -26321,14 +26319,14 @@
} }
}, },
"http-signature": { "http-signature": {
"version": "1.3.6", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.3.6.tgz", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz",
"integrity": "sha512-3adrsD6zqo4GsTqtO7FyrejHNv+NgiIfAfv68+jVlFmSr9OGy7zrxONceFRLKvnnZA5jbxQBX1u9PpB6Wi32Gw==", "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==",
"optional": true, "optional": true,
"requires": { "requires": {
"assert-plus": "^1.0.0", "assert-plus": "^1.0.0",
"jsprim": "^2.0.2", "jsprim": "^2.0.2",
"sshpk": "^1.14.1" "sshpk": "^1.18.0"
} }
}, },
"https-browserify": { "https-browserify": {
@ -29098,12 +29096,11 @@
} }
}, },
"qs": { "qs": {
"version": "6.10.4", "version": "6.13.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.4.tgz", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz",
"integrity": "sha512-OQiU+C+Ds5qiH91qh/mg0w+8nwQuLjM4F4M/PbmhDOoYehPh+Fb0bDjtR1sOvy7YKxvj28Y/M0PhP5uVX0kB+g==", "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==",
"optional": true,
"requires": { "requires": {
"side-channel": "^1.0.4" "side-channel": "^1.0.6"
} }
}, },
"querystring": { "querystring": {
@ -29456,24 +29453,27 @@
} }
}, },
"rollup": { "rollup": {
"version": "4.13.0", "version": "4.24.0",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.13.0.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.24.0.tgz",
"integrity": "sha512-3YegKemjoQnYKmsBlOHfMLVPPA5xLkQ8MHLLSw/fBrFaVkEayL51DilPpNNLq1exr98F2B1TzrV0FUlN3gWRPg==", "integrity": "sha512-DOmrlGSXNk1DM0ljiQA+i+o0rSLhtii1je5wgk60j49d1jHT5YYttBv1iWOnYSTG+fZZESUOSNiAl89SIet+Cg==",
"requires": { "requires": {
"@rollup/rollup-android-arm-eabi": "4.13.0", "@rollup/rollup-android-arm-eabi": "4.24.0",
"@rollup/rollup-android-arm64": "4.13.0", "@rollup/rollup-android-arm64": "4.24.0",
"@rollup/rollup-darwin-arm64": "4.13.0", "@rollup/rollup-darwin-arm64": "4.24.0",
"@rollup/rollup-darwin-x64": "4.13.0", "@rollup/rollup-darwin-x64": "4.24.0",
"@rollup/rollup-linux-arm-gnueabihf": "4.13.0", "@rollup/rollup-linux-arm-gnueabihf": "4.24.0",
"@rollup/rollup-linux-arm64-gnu": "4.13.0", "@rollup/rollup-linux-arm-musleabihf": "4.24.0",
"@rollup/rollup-linux-arm64-musl": "4.13.0", "@rollup/rollup-linux-arm64-gnu": "4.24.0",
"@rollup/rollup-linux-riscv64-gnu": "4.13.0", "@rollup/rollup-linux-arm64-musl": "4.24.0",
"@rollup/rollup-linux-x64-gnu": "4.13.0", "@rollup/rollup-linux-powerpc64le-gnu": "4.24.0",
"@rollup/rollup-linux-x64-musl": "4.13.0", "@rollup/rollup-linux-riscv64-gnu": "4.24.0",
"@rollup/rollup-win32-arm64-msvc": "4.13.0", "@rollup/rollup-linux-s390x-gnu": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.13.0", "@rollup/rollup-linux-x64-gnu": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.13.0", "@rollup/rollup-linux-x64-musl": "4.24.0",
"@types/estree": "1.0.5", "@rollup/rollup-win32-arm64-msvc": "4.24.0",
"@rollup/rollup-win32-ia32-msvc": "4.24.0",
"@rollup/rollup-win32-x64-msvc": "4.24.0",
"@types/estree": "1.0.6",
"fsevents": "~2.3.2" "fsevents": "~2.3.2"
} }
}, },
@ -30167,9 +30167,9 @@
"integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==" "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA=="
}, },
"sshpk": { "sshpk": {
"version": "1.17.0", "version": "1.18.0",
"resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz",
"integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==",
"optional": true, "optional": true,
"requires": { "requires": {
"asn1": "~0.2.3", "asn1": "~0.2.3",
@ -30615,9 +30615,9 @@
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
}, },
"tough-cookie": { "tough-cookie": {
"version": "4.1.3", "version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.3.tgz", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==", "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"optional": true, "optional": true,
"requires": { "requires": {
"psl": "^1.1.33", "psl": "^1.1.33",
@ -31248,17 +31248,6 @@
"proxy-from-env": "^1.1.0" "proxy-from-env": "^1.1.0"
} }
}, },
"form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"optional": true,
"requires": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
}
},
"proxy-from-env": { "proxy-from-env": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",

View File

@ -115,7 +115,7 @@
"optionalDependencies": { "optionalDependencies": {
"@cypress/schematic": "^2.5.0", "@cypress/schematic": "^2.5.0",
"@types/cypress": "^1.1.3", "@types/cypress": "^1.1.3",
"cypress": "^13.14.0", "cypress": "^13.15.0",
"cypress-fail-on-console-error": "~5.1.0", "cypress-fail-on-console-error": "~5.1.0",
"cypress-wait-until": "^2.0.1", "cypress-wait-until": "^2.0.1",
"mock-socket": "~9.3.1", "mock-socket": "~9.3.1",

View File

@ -21,6 +21,7 @@ import { StorageService } from './services/storage.service';
import { HttpCacheInterceptor } from './services/http-cache.interceptor'; import { HttpCacheInterceptor } from './services/http-cache.interceptor';
import { LanguageService } from './services/language.service'; import { LanguageService } from './services/language.service';
import { ThemeService } from './services/theme.service'; import { ThemeService } from './services/theme.service';
import { TimeService } from './services/time.service';
import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe'; import { FiatShortenerPipe } from './shared/pipes/fiat-shortener.pipe';
import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe'; import { FiatCurrencyPipe } from './shared/pipes/fiat-currency.pipe';
import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe'; import { ShortenStringPipe } from './shared/pipes/shorten-string-pipe/shorten-string.pipe';
@ -42,6 +43,7 @@ const providers = [
EnterpriseService, EnterpriseService,
LanguageService, LanguageService,
ThemeService, ThemeService,
TimeService,
ShortenStringPipe, ShortenStringPipe,
FiatShortenerPipe, FiatShortenerPipe,
FiatCurrencyPipe, FiatCurrencyPipe,

View File

@ -38,7 +38,7 @@
<div class="node-spacer"></div> <div class="node-spacer"></div>
<div class="interval"> <div class="interval">
<div class="interval-time"> <div class="interval-time">
<app-time [time]="acceleratedAt - transactionTime"></app-time> <app-time [time]="firstSeenToAccelerated"></app-time>
</div> </div>
</div> </div>
<div class="node-spacer"></div> <div class="node-spacer"></div>
@ -46,7 +46,7 @@
<div class="interval-time"> <div class="interval-time">
@if (tx.status.confirmed) { @if (tx.status.confirmed) {
<div class="interval-time"> <div class="interval-time">
<app-time [time]="tx.status.block_time - acceleratedAt"></app-time> <app-time [time]="acceleratedToMined"></app-time>
</div> </div>
} @else if (standardETA && !tx.status.confirmed) { } @else if (standardETA && !tx.status.confirmed) {
<!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> --> <!-- ~<app-time [time]="standardETA / 1000 - now"></app-time> -->

View File

@ -24,6 +24,8 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
accelerateRatio: number; accelerateRatio: number;
useAbsoluteTime: boolean = false; useAbsoluteTime: boolean = false;
interval: number; interval: number;
firstSeenToAccelerated: number;
acceleratedToMined: number;
tooltipPosition = null; tooltipPosition = null;
hoverInfo: any = null; hoverInfo: any = null;
@ -35,8 +37,6 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
ngOnInit(): void { ngOnInit(): void {
this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000; this.acceleratedAt = this.tx.acceleratedAt ?? new Date().getTime() / 1000;
this.now = Math.floor(new Date().getTime() / 1000);
this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
this.miningService.getPools().subscribe(pools => { this.miningService.getPools().subscribe(pools => {
for (const pool of pools) { for (const pool of pools) {
@ -44,10 +44,8 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
} }
}); });
this.interval = window.setInterval(() => { this.updateTimes();
this.now = Math.floor(new Date().getTime() / 1000); this.interval = window.setInterval(this.updateTimes.bind(this), 60000);
this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
}, 60000);
} }
ngOnChanges(changes): void { ngOnChanges(changes): void {
@ -64,6 +62,13 @@ export class AccelerationTimelineComponent implements OnInit, OnChanges {
// } // }
} }
updateTimes(): void {
this.now = Math.floor(new Date().getTime() / 1000);
this.useAbsoluteTime = this.tx.status.block_time < this.now - 7 * 24 * 3600;
this.firstSeenToAccelerated = Math.max(0, this.acceleratedAt - this.transactionTime);
this.acceleratedToMined = Math.max(0, this.tx.status.block_time - this.acceleratedAt);
}
ngOnDestroy(): void { ngOnDestroy(): void {
clearInterval(this.interval); clearInterval(this.interval);
} }

View File

@ -64,7 +64,7 @@
<span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span> <span *ngIf="acceleration.status === 'accelerating'" class="badge badge-warning" i18n="accelerator.pending">Pending</span>
<span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span> <span *ngIf="acceleration.status.includes('completed') && acceleration.minedByPoolUniqueId && pools[acceleration.minedByPoolUniqueId]" class="badge badge-success"><ng-container i18n="accelerator.completed">Completed</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span> <span *ngIf="acceleration.status.includes('completed') && (!acceleration.minedByPoolUniqueId || !pools[acceleration.minedByPoolUniqueId])" class="badge badge-success"><ng-container i18n="transaction.rbf.mined">Mined</ng-container><span *ngIf="acceleration.status === 'completed_provisional'">&nbsp;</span></span>
<span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Failed</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;</span></span> <span *ngIf="acceleration.status.includes('failed')" class="badge badge-danger"><ng-container i18n="accelerator.canceled">Canceled</ng-container><span *ngIf="acceleration.status === 'failed_provisional'">&nbsp;</span></span>
</td> </td>
<td class="date text-right" *ngIf="!this.widget"> <td class="date text-right" *ngIf="!this.widget">
<app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time> <app-time kind="since" [time]="acceleration.added" [fastRender]="true" [showTooltip]="true"></app-time>

View File

@ -219,9 +219,13 @@ export class AddressComponent implements OnInit, OnDestroy {
address.is_pubkey address.is_pubkey
? this.electrsApiService.getScriptHashTransactions$((address.address.length === 66 ? '21' : '41') + address.address + 'ac') ? this.electrsApiService.getScriptHashTransactions$((address.address.length === 66 ? '21' : '41') + address.address + 'ac')
: this.electrsApiService.getAddressTransactions$(address.address), : this.electrsApiService.getAddressTransactions$(address.address),
utxoCount >= 2 && utxoCount <= 500 ? (address.is_pubkey (utxoCount > 2 && utxoCount <= 500 ? (address.is_pubkey
? this.electrsApiService.getScriptHashUtxos$((address.address.length === 66 ? '21' : '41') + address.address + 'ac') ? this.electrsApiService.getScriptHashUtxos$((address.address.length === 66 ? '21' : '41') + address.address + 'ac')
: this.electrsApiService.getAddressUtxos$(address.address)) : of([]) : this.electrsApiService.getAddressUtxos$(address.address)) : of(null)).pipe(
catchError(() => {
return of(null);
})
)
]); ]);
}), }),
switchMap(([transactions, utxos]) => { switchMap(([transactions, utxos]) => {
@ -319,6 +323,7 @@ export class AddressComponent implements OnInit, OnDestroy {
this.transactions = this.transactions.slice(); this.transactions = this.transactions.slice();
this.mempoolStats.removeTx(transaction); this.mempoolStats.removeTx(transaction);
this.audioService.playSound('magic'); this.audioService.playSound('magic');
this.confirmTransaction(tx);
} else { } else {
if (this.addTransaction(transaction, false)) { if (this.addTransaction(transaction, false)) {
this.audioService.playSound('magic'); this.audioService.playSound('magic');
@ -345,10 +350,13 @@ export class AddressComponent implements OnInit, OnDestroy {
} }
// update utxos in-place // update utxos in-place
if (this.utxos != null) {
let utxosChanged = false;
for (const vin of transaction.vin) { for (const vin of transaction.vin) {
const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === vin.txid && utxo.vout === vin.vout); const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === vin.txid && utxo.vout === vin.vout);
if (utxoIndex !== -1) { if (utxoIndex !== -1) {
this.utxos.splice(utxoIndex, 1); this.utxos.splice(utxoIndex, 1);
utxosChanged = true;
} }
} }
for (const [index, vout] of transaction.vout.entries()) { for (const [index, vout] of transaction.vout.entries()) {
@ -359,6 +367,11 @@ export class AddressComponent implements OnInit, OnDestroy {
value: vout.value, value: vout.value,
status: JSON.parse(JSON.stringify(transaction.status)), status: JSON.parse(JSON.stringify(transaction.status)),
}); });
utxosChanged = true;
}
}
if (utxosChanged) {
this.utxos = this.utxos.slice();
} }
} }
return true; return true;
@ -374,6 +387,8 @@ export class AddressComponent implements OnInit, OnDestroy {
this.transactions = this.transactions.slice(); this.transactions = this.transactions.slice();
// update utxos in-place // update utxos in-place
if (this.utxos != null) {
let utxosChanged = false;
for (const vin of transaction.vin) { for (const vin of transaction.vin) {
if (vin.prevout?.scriptpubkey_address === this.address.address) { if (vin.prevout?.scriptpubkey_address === this.address.address) {
this.utxos.push({ this.utxos.push({
@ -382,6 +397,7 @@ export class AddressComponent implements OnInit, OnDestroy {
value: vin.prevout.value, value: vin.prevout.value,
status: { confirmed: true }, // Assuming the input was confirmed status: { confirmed: true }, // Assuming the input was confirmed
}); });
utxosChanged = true;
} }
} }
for (const [index, vout] of transaction.vout.entries()) { for (const [index, vout] of transaction.vout.entries()) {
@ -389,13 +405,46 @@ export class AddressComponent implements OnInit, OnDestroy {
const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === transaction.txid && utxo.vout === index); const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === transaction.txid && utxo.vout === index);
if (utxoIndex !== -1) { if (utxoIndex !== -1) {
this.utxos.splice(utxoIndex, 1); this.utxos.splice(utxoIndex, 1);
utxosChanged = true;
} }
} }
} }
if (utxosChanged) {
this.utxos = this.utxos.slice();
}
}
return true; return true;
} }
confirmTransaction(transaction: Transaction): void {
// update utxos in-place
if (this.utxos != null) {
let utxosChanged = false;
for (const vin of transaction.vin) {
if (vin.prevout?.scriptpubkey_address === this.address.address) {
const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === vin.txid && utxo.vout === vin.vout);
if (utxoIndex !== -1) {
this.utxos[utxoIndex].status = JSON.parse(JSON.stringify(transaction.status));
utxosChanged = true;
}
}
}
for (const [index, vout] of transaction.vout.entries()) {
if (vout.scriptpubkey_address === this.address.address) {
const utxoIndex = this.utxos.findIndex((utxo) => utxo.txid === transaction.txid && utxo.vout === index);
if (utxoIndex !== -1) {
this.utxos[utxoIndex].status = JSON.parse(JSON.stringify(transaction.status));
utxosChanged = true;
}
}
}
if (utxosChanged) {
this.utxos = this.utxos.slice();
}
}
}
loadMore(): void { loadMore(): void {
if (this.isLoadingTransactions || this.fullyLoaded) { if (this.isLoadingTransactions || this.fullyLoaded) {
return; return;

View File

@ -30,7 +30,7 @@
@if (digitsInfo === '1.8-8') { @if (digitsInfo === '1.8-8') {
&lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number }} &lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | number }}
} @else { } @else {
&lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | amountShortener : satoshis < 1000 && satoshis > -1000 ? 0 : 1 }} &lrm;{{ addPlus && satoshis >= 0 ? '+' : '' }}{{ satoshis | amountShortener : (satoshis < 1000 && satoshis > -1000 ? 0 : 1) : undefined : true }}
} }
<span class="symbol"> <span class="symbol">
<ng-container *ngTemplateOutlet="prefix"></ng-container>sats <ng-container *ngTemplateOutlet="prefix"></ng-container>sats

View File

@ -11,6 +11,10 @@ export function hexToColor(hex: string): Color {
}; };
} }
export function colorToHex(color: Color): string {
return [color.r, color.g, color.b].map(c => Math.round(c * 255).toString(16)).join('');
}
export function desaturate(color: Color, amount: number): Color { export function desaturate(color: Color, amount: number): Color {
const gray = (color.r + color.g + color.b) / 6; const gray = (color.r + color.g + color.b) / 6;
return { return {
@ -30,6 +34,15 @@ export function darken(color: Color, amount: number): Color {
}; };
} }
export function mix(color1: Color, color2: Color, amount: number): Color {
return {
r: color1.r * (1 - amount) + color2.r * amount,
g: color1.g * (1 - amount) + color2.g * amount,
b: color1.b * (1 - amount) + color2.b * amount,
a: color1.a * (1 - amount) + color2.a * amount,
};
}
export function setOpacity(color: Color, opacity: number): Color { export function setOpacity(color: Color, opacity: number): Color {
return { return {
...color, ...color,

View File

@ -53,6 +53,13 @@
<td i18n="block.miner">Miner</td> <td i18n="block.miner">Miner</td>
<td *ngIf="stateService.env.MINING_DASHBOARD"> <td *ngIf="stateService.env.MINING_DASHBOARD">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;"> <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.name }} {{ block.extras.pool.name }}
</a> </a>
@ -60,7 +67,14 @@
<td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'"> <td *ngIf="!stateService.env.MINING_DASHBOARD && stateService.env.BASE_MODULE === 'mempool'">
<span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge" <span [attr.data-cy]="'block-details-miner-badge'" placement="bottom" class="badge"
[class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'"> [class]="!block?.extras.pool.name || block?.extras.pool.slug === 'unknown' ? 'badge-secondary' : 'badge-primary'">
{{ block?.extras.pool.name }} <span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
{{ block.extras.pool.name }}
</span> </span>
</td> </td>
</tr> </tr>

View File

@ -66,10 +66,10 @@
[class.badge-success]="blockAudit?.matchRate >= 99" [class.badge-success]="blockAudit?.matchRate >= 99"
[class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99" [class.badge-warning]="blockAudit?.matchRate >= 75 && blockAudit?.matchRate < 99"
[class.badge-danger]="blockAudit?.matchRate < 75" [class.badge-danger]="blockAudit?.matchRate < 75"
*ngIf="blockAudit?.matchRate != null; else nullHealth" *ngIf="blockAudit?.matchRate != null && blockAudit?.id === block.id; else nullHealth"
>{{ blockAudit?.matchRate }}%</span> >{{ blockAudit?.matchRate }}%</span>
<ng-template #nullHealth> <ng-template #nullHealth>
<ng-container *ngIf="!isLoadingOverview; else loadingHealth"> <ng-container *ngIf="!isLoadingOverview && blockAudit?.id === block.id; else loadingHealth">
<span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span> <span class="health-badge badge badge-secondary" i18n="unknown">Unknown</span>
</ng-container> </ng-container>
</ng-template> </ng-template>
@ -182,6 +182,13 @@
<td i18n="block.miner">Miner</td> <td i18n="block.miner">Miner</td>
<td *ngIf="stateService.env.MINING_DASHBOARD"> <td *ngIf="stateService.env.MINING_DASHBOARD">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;"> <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, block.extras.pool.slug]" class="badge" style="color: #FFF;padding:0;">
<span class="miner-name" *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''">
@if (block.extras.pool.minerNames[1].length > 16) {
{{ block.extras.pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ block.extras.pool.minerNames[1] }}
}
</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> <img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.name }} {{ block.extras.pool.name }}
</a> </a>

View File

@ -81,6 +81,19 @@ h1 {
} }
} }
.miner-name {
margin-right: 4px;
vertical-align: top;
}
.pool-logo {
width: 25px;
height: 25px;
position: relative;
top: -1px;
margin-right: 2px;
}
.row { .row {
flex-direction: column; flex-direction: column;
@media (min-width: 768px) { @media (min-width: 768px) {

View File

@ -60,9 +60,14 @@
</ng-container> </ng-container>
</div> </div>
<div class="animated" *ngIf="block.extras?.pool != undefined && showPools"> <div class="animated" *ngIf="block.extras?.pool != undefined && showPools">
<a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]"> <a [attr.data-cy]="'bitcoin-block-' + offset + '-index-' + i + '-pool'" class="badge" [class.miner-name]="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''" [routerLink]="[('/mining/pool/' + block.extras.pool.slug) | relativeUrl]">
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> <ng-container *ngIf="block.extras.pool.minerNames?.length > 1 && block.extras.pool.minerNames[1] != ''; else centralisedPool">
{{ block.extras.pool.name}} <img [ngbTooltip]="block.extras.pool.name" class="pool-logo faded" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'">
{{ block.extras.pool.minerNames[1] }}
</ng-container>
<ng-template #centralisedPool>
<img class="pool-logo" [src]="'/resources/mining-pools/' + block.extras.pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + block.extras.pool.name + ' mining pool'"> {{ block.extras.pool.name }}
</ng-template>
</a> </a>
</div> </div>
</div> </div>

View File

@ -19,6 +19,38 @@
pointer-events: none; pointer-events: none;
} }
.on-pool-name-text {
display: inline-block;
padding-top: 2px;
font-weight: normal;
}
.on-pool {
align-items: center;
background-color: var(--bg);
display: inline-block;
margin-top: 4px;
padding: .25em .4em;
border-radius: .25rem;
}
.on-pool-container {
align-items: center;
position: relative;
top: -8px;
display: flex;
flex-direction: column;
}
.on-pool-container.selected {
top: 0px;
}
.pool-container {
margin-top: 12px;
}
.mined-block { .mined-block {
position: absolute; position: absolute;
top: 0px; top: 0px;
@ -155,9 +187,16 @@
.badge { .badge {
position: relative; position: relative;
top: 15px; top: 19px;
z-index: 101; z-index: 101;
color: #FFF; color: #FFF;
overflow: hidden;
text-overflow: ellipsis;
max-width: 145px;
&.miner-name {
max-width: 125px;
}
} }
.pool-logo { .pool-logo {
@ -168,6 +207,10 @@
margin-right: 2px; margin-right: 2px;
} }
.pool-logo.faded {
filter: grayscale(100%) brightness(1.5);
}
.animated { .animated {
transition: all 0.15s ease-in-out; transition: all 0.15s ease-in-out;
white-space: nowrap; white-space: nowrap;

View File

@ -4,8 +4,8 @@
<div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;"> <div *ngIf="!widget" class="float-left" style="display: flex; width: 100%; align-items: center;">
<h1 i18n="master-page.blocks">Blocks</h1> <h1 i18n="master-page.blocks">Blocks</h1>
<app-svg-images name="blocks-2-3" style="width: 275px; max-width: 90%; margin-top: -10px"></app-svg-images> <app-svg-images name="blocks-2-3" style="width: 275px; max-width: 90%; margin-top: -10px"></app-svg-images>
<div *ngIf="!widget && isLoading" class="spinner-border" role="status"></div>
</div> </div>
<div *ngIf="!widget && isLoading" class="spinner-border ml-3" role="status"></div>
<div class="clearfix"></div> <div class="clearfix"></div>

View File

@ -1,7 +1,9 @@
.spinner-border { .spinner-border {
height: 25px; height: 25px;
width: 25px; width: 25px;
margin-top: 13px; margin-top: -10px;
margin-left: -13px;
flex-shrink: 0;
} }
.container-xl { .container-xl {

View File

@ -85,7 +85,6 @@
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="network.val === '' && stateService.env.ACCELERATOR"> <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-home" *ngIf="network.val === '' && stateService.env.ACCELERATOR">
<a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()"> <a class="nav-link" [routerLink]="['/acceleration' | relativeUrl]" (click)="collapse()">
<fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon> <fa-icon [icon]="['fas', 'rocket']" [fixedWidth]="true" i18n-title="master-page.accelerator-dashboard" title="Accelerator Dashboard"></fa-icon>
<span class="badge badge-pill badge-warning beta" i18n="beta">beta</span>
</a> </a>
</li> </li>
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD"> <li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{exact: true}" id="btn-pools" *ngIf="stateService.env.MINING_DASHBOARD">

View File

@ -1,7 +1,6 @@
import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core'; import { Component, OnInit, OnDestroy, ChangeDetectionStrategy, Input, ChangeDetectorRef, OnChanges } from '@angular/core';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { dates } from '../../shared/i18n/dates'; import { TimeService } from '../../services/time.service';
import { DatePipe } from '@angular/common';
@Component({ @Component({
selector: 'app-time', selector: 'app-time',
@ -12,19 +11,9 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
interval: number; interval: number;
text: string; text: string;
tooltip: string; tooltip: string;
precisionThresholds = {
year: 100,
month: 18,
week: 12,
day: 31,
hour: 48,
minute: 90,
second: 90
};
intervals = {};
@Input() time: number; @Input() time: number;
@Input() dateString: number; @Input() dateString: string;
@Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within' = 'plain'; @Input() kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within' = 'plain';
@Input() fastRender = false; @Input() fastRender = false;
@Input() fixedRender = false; @Input() fixedRender = false;
@ -40,37 +29,26 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
constructor( constructor(
private ref: ChangeDetectorRef, private ref: ChangeDetectorRef,
private stateService: StateService, private stateService: StateService,
private datePipe: DatePipe, private timeService: TimeService,
) { ) {}
this.intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
}
ngOnInit() { ngOnInit() {
this.calculateTime();
if(this.fixedRender){ if(this.fixedRender){
this.text = this.calculate();
return; return;
} }
if (!this.stateService.isBrowser) { if (!this.stateService.isBrowser) {
this.text = this.calculate();
this.ref.markForCheck(); this.ref.markForCheck();
return; return;
} }
this.interval = window.setInterval(() => { this.interval = window.setInterval(() => {
this.text = this.calculate(); this.calculateTime();
this.ref.markForCheck(); this.ref.markForCheck();
}, 1000 * (this.fastRender ? 1 : 60)); }, 1000 * (this.fastRender ? 1 : 60));
} }
ngOnChanges() { ngOnChanges() {
this.text = this.calculate(); this.calculateTime();
this.ref.markForCheck(); this.ref.markForCheck();
} }
@ -78,224 +56,21 @@ export class TimeComponent implements OnInit, OnChanges, OnDestroy {
clearInterval(this.interval); clearInterval(this.interval);
} }
calculate() { calculateTime(): void {
if (this.time == null) { const { text, tooltip } = this.timeService.calculate(
return; this.time,
} this.kind,
this.relative,
let seconds: number; this.precision,
switch (this.kind) { this.minUnit,
case 'since': this.showTooltip,
seconds = Math.floor((+new Date() - +new Date(this.dateString || this.time * 1000)) / 1000); this.units,
this.tooltip = this.datePipe.transform(new Date(this.dateString || this.time * 1000), 'yyyy-MM-dd HH:mm'); this.dateString,
break; this.lowercaseStart,
case 'until': this.numUnits,
case 'within': this.fractionDigits,
seconds = (+new Date(this.time) - +new Date()) / 1000; );
this.tooltip = this.datePipe.transform(new Date(this.time), 'yyyy-MM-dd HH:mm'); this.text = text;
break; this.tooltip = tooltip;
default:
seconds = Math.floor(this.time);
this.tooltip = '';
}
if (!this.showTooltip || this.relative) {
this.tooltip = '';
}
if (seconds < 1 && this.kind === 'span') {
return $localize`:@@date-base.immediately:Immediately`;
} else if (seconds < 60) {
if (this.relative || this.kind === 'since') {
if (this.lowercaseStart) {
return $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1);
}
return $localize`:@@date-base.just-now:Just now`;
} else if (this.kind === 'until' || this.kind === 'within') {
seconds = 60;
}
}
let counter: number;
const result = [];
let usedUnits = 0;
for (const [index, unit] of this.units.entries()) {
let precisionUnit = this.units[Math.min(this.units.length - 1, index + this.precision)];
counter = Math.floor(seconds / this.intervals[unit]);
const precisionCounter = Math.round(seconds / this.intervals[precisionUnit]);
if (precisionCounter > this.precisionThresholds[precisionUnit]) {
precisionUnit = unit;
}
if (this.units.indexOf(precisionUnit) === this.units.indexOf(this.minUnit)) {
counter = Math.max(1, counter);
}
if (counter > 0) {
let rounded;
const roundFactor = Math.pow(10,this.fractionDigits || 0);
if ((this.kind === 'until' || this.kind === 'within') && usedUnits < this.numUnits) {
rounded = Math.floor((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
} else {
rounded = Math.round((seconds / this.intervals[precisionUnit]) * roundFactor) / roundFactor;
}
if ((this.kind !== 'until' && this.kind !== 'within')|| this.numUnits === 1) {
return this.formatTime(this.kind, precisionUnit, rounded);
} else {
if (!usedUnits) {
result.push(this.formatTime(this.kind, precisionUnit, rounded));
} else {
result.push(this.formatTime('', precisionUnit, rounded));
}
seconds -= (rounded * this.intervals[precisionUnit]);
usedUnits++;
if (usedUnits >= this.numUnits) {
return result.join(', ');
}
}
}
}
return result.join(', ');
}
private formatTime(kind, unit, number): string {
const dateStrings = dates(number);
switch (kind) {
case 'since':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
}
}
break;
case 'until':
if (number === 1) {
switch (unit) { // singular (In ~1 day)
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
}
} else {
switch (unit) { // plural (In ~2 days)
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'within':
if (number === 1) {
switch (unit) { // singular (In ~1 day)
case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`;
case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`;
}
} else {
switch (unit) { // plural (In ~2 days)
case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'span':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'before':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-before:${dateStrings.i18nYear}:DATE: before`; break;
case 'month': return $localize`:@@time-before:${dateStrings.i18nMonth}:DATE: before`; break;
case 'week': return $localize`:@@time-before:${dateStrings.i18nWeek}:DATE: before`; break;
case 'day': return $localize`:@@time-before:${dateStrings.i18nDay}:DATE: before`; break;
case 'hour': return $localize`:@@time-before:${dateStrings.i18nHour}:DATE: before`; break;
case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinute}:DATE: before`; break;
case 'second': return $localize`:@@time-before:${dateStrings.i18nSecond}:DATE: before`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-before:${dateStrings.i18nYears}:DATE: before`; break;
case 'month': return $localize`:@@time-before:${dateStrings.i18nMonths}:DATE: before`; break;
case 'week': return $localize`:@@time-before:${dateStrings.i18nWeeks}:DATE: before`; break;
case 'day': return $localize`:@@time-before:${dateStrings.i18nDays}:DATE: before`; break;
case 'hour': return $localize`:@@time-before:${dateStrings.i18nHours}:DATE: before`; break;
case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinutes}:DATE: before`; break;
case 'second': return $localize`:@@time-before:${dateStrings.i18nSeconds}:DATE: before`; break;
}
}
break;
default:
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return dateStrings.i18nYear; break;
case 'month': return dateStrings.i18nMonth; break;
case 'week': return dateStrings.i18nWeek; break;
case 'day': return dateStrings.i18nDay; break;
case 'hour': return dateStrings.i18nHour; break;
case 'minute': return dateStrings.i18nMinute; break;
case 'second': return dateStrings.i18nSecond; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return dateStrings.i18nYears; break;
case 'month': return dateStrings.i18nMonths; break;
case 'week': return dateStrings.i18nWeeks; break;
case 'day': return dateStrings.i18nDays; break;
case 'hour': return dateStrings.i18nHours; break;
case 'minute': return dateStrings.i18nMinutes; break;
case 'second': return dateStrings.i18nSeconds; break;
}
}
}
} }
} }

View File

@ -684,6 +684,13 @@
@if (pool) { @if (pool) {
<td class="wrap-cell"> <td class="wrap-cell">
<a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;padding:0;"> <a placement="bottom" [routerLink]="['/mining/pool' | relativeUrl, pool.slug]" class="badge" style="color: #FFF;padding:0;">
<span class="miner-name" *ngIf="pool.minerNames?.length > 1 && pool.minerNames[1] != ''">
@if (pool.minerNames[1].length > 16) {
{{ pool.minerNames[1].slice(0, 15) }}…
} @else {
{{ pool.minerNames[1] }}
}
</span>
<img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'"> <img class="pool-logo" [src]="'/resources/mining-pools/' + pool.slug + '.svg'" onError="this.src = '/resources/mining-pools/default.svg'" [alt]="'Logo of ' + pool.name + ' mining pool'">
{{ pool.name }} {{ pool.name }}
</a> </a>

View File

@ -60,6 +60,19 @@
top: -1px; top: -1px;
} }
.miner-name {
margin-right: 4px;
vertical-align: top;
}
.pool-logo {
width: 25px;
height: 25px;
position: relative;
top: -1px;
margin-right: 2px;
}
.badge.badge-accelerated { .badge.badge-accelerated {
background-color: var(--tertiary); background-color: var(--tertiary);
color: white; color: white;

View File

@ -42,6 +42,7 @@ interface Pool {
id: number; id: number;
name: string; name: string;
slug: string; slug: string;
minerNames: string[] | null;
} }
export interface TxAuditStatus { export interface TxAuditStatus {

View File

@ -1,12 +1,44 @@
import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgZone, OnChanges, OnDestroy, SimpleChanges } from '@angular/core'; import { ChangeDetectionStrategy, ChangeDetectorRef, Component, Input, NgZone, OnChanges, OnDestroy, SimpleChanges } from '@angular/core';
import { EChartsOption } from '../../graphs/echarts'; import { EChartsOption } from '../../graphs/echarts';
import { BehaviorSubject, Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { Utxo } from '../../interfaces/electrs.interface'; import { Utxo } from '../../interfaces/electrs.interface';
import { StateService } from '../../services/state.service'; import { StateService } from '../../services/state.service';
import { Router } from '@angular/router'; import { Router } from '@angular/router';
import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe'; import { RelativeUrlPipe } from '../../shared/pipes/relative-url/relative-url.pipe';
import { renderSats } from '../../shared/common.utils'; import { renderSats } from '../../shared/common.utils';
import { colorToHex, hexToColor, mix } from '../block-overview-graph/utils';
import { TimeService } from '../../services/time.service';
const newColorHex = '1bd8f4';
const oldColorHex = '9339f4';
const pendingColorHex = 'eba814';
const newColor = hexToColor(newColorHex);
const oldColor = hexToColor(oldColorHex);
interface Circle {
x: number,
y: number,
r: number,
i: number,
}
interface UtxoCircle extends Circle {
utxo: Utxo;
}
function sortedInsert(positions: { c1: Circle, c2: Circle, d: number, p: number, side?: boolean }[], newPosition: { c1: Circle, c2: Circle, d: number, p: number }): void {
let left = 0;
let right = positions.length;
while (left < right) {
const mid = Math.floor((left + right) / 2);
if (positions[mid].p > newPosition.p) {
right = mid;
} else {
left = mid + 1;
}
}
positions.splice(left, 0, newPosition, {...newPosition, side: true });
}
@Component({ @Component({
selector: 'app-utxo-graph', selector: 'app-utxo-graph',
templateUrl: './utxo-graph.component.html', templateUrl: './utxo-graph.component.html',
@ -29,7 +61,8 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
@Input() widget: boolean = false; @Input() widget: boolean = false;
subscription: Subscription; subscription: Subscription;
redraw$: BehaviorSubject<boolean> = new BehaviorSubject(false); lastUpdate: number = 0;
updateInterval;
chartOptions: EChartsOption = {}; chartOptions: EChartsOption = {};
chartInitOptions = { chartInitOptions = {
@ -46,7 +79,15 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
private zone: NgZone, private zone: NgZone,
private router: Router, private router: Router,
private relativeUrlPipe: RelativeUrlPipe, private relativeUrlPipe: RelativeUrlPipe,
) {} private timeService: TimeService,
) {
// re-render the chart every 10 seconds, to keep the age colors up to date
this.updateInterval = setInterval(() => {
if (this.lastUpdate < Date.now() - 10000 && this.utxos) {
this.prepareChartOptions(this.utxos);
}
}, 10000);
}
ngOnChanges(changes: SimpleChanges): void { ngOnChanges(changes: SimpleChanges): void {
this.isLoading = true; this.isLoading = true;
@ -58,7 +99,7 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
} }
} }
prepareChartOptions(utxos: Utxo[]) { prepareChartOptions(utxos: Utxo[]): void {
if (!utxos || utxos.length === 0) { if (!utxos || utxos.length === 0) {
return; return;
} }
@ -67,94 +108,110 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
// Helper functions // Helper functions
const distance = (x1: number, y1: number, x2: number, y2: number): number => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2); const distance = (x1: number, y1: number, x2: number, y2: number): number => Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2);
const intersectionPoints = (x1: number, y1: number, r1: number, x2: number, y2: number, r2: number): [number, number][] => { const intersection = (c1: Circle, c2: Circle, d: number, r: number, side: boolean): { x: number, y: number} => {
const d = distance(x1, y1, x2, y2); const d1 = c1.r + r;
const a = (r1 * r1 - r2 * r2 + d * d) / (2 * d); const d2 = c2.r + r;
const h = Math.sqrt(r1 * r1 - a * a); const a = (d1 * d1 - d2 * d2 + d * d) / (2 * d);
const x3 = x1 + a * (x2 - x1) / d; const h = Math.sqrt(d1 * d1 - a * a);
const y3 = y1 + a * (y2 - y1) / d; const x3 = c1.x + a * (c2.x - c1.x) / d;
return [ const y3 = c1.y + a * (c2.y - c1.y) / d;
[x3 + h * (y2 - y1) / d, y3 - h * (x2 - x1) / d], return side
[x3 - h * (y2 - y1) / d, y3 + h * (x2 - x1) / d] ? { x: x3 + h * (c2.y - c1.y) / d, y: y3 - h * (c2.x - c1.x) / d }
]; : { x: x3 - h * (c2.y - c1.y) / d, y: y3 + h * (c2.x - c1.x) / d };
}; };
// Naive algorithm to pack circles as tightly as possible without overlaps // ~Linear algorithm to pack circles as tightly as possible without overlaps
const placedCircles: { x: number, y: number, r: number, utxo: Utxo, distances: number[] }[] = []; const placedCircles: UtxoCircle[] = [];
const positions: { c1: Circle, c2: Circle, d: number, p: number, side?: boolean }[] = [];
// Pack in descending order of value, and limit to the top 500 to preserve performance // Pack in descending order of value, and limit to the top 500 to preserve performance
const sortedUtxos = utxos.sort((a, b) => b.value - a.value).slice(0, 500); const sortedUtxos = utxos.sort((a, b) => {
let centerOfMass = { x: 0, y: 0 }; if (a.value === b.value) {
let weightOfMass = 0; if (a.status.confirmed && !b.status.confirmed) {
return -1;
} else if (!a.status.confirmed && b.status.confirmed) {
return 1;
} else {
return a.status.block_height - b.status.block_height;
}
}
return b.value - a.value;
}).slice(0, 500);
const maxR = Math.sqrt(sortedUtxos.reduce((max, utxo) => Math.max(max, utxo.value), 0));
sortedUtxos.forEach((utxo, index) => { sortedUtxos.forEach((utxo, index) => {
// area proportional to value // area proportional to value
const r = Math.sqrt(utxo.value); const r = Math.sqrt(utxo.value);
// special cases for the first two utxos // special cases for the first two utxos
if (index === 0) { if (index === 0) {
placedCircles.push({ x: 0, y: 0, r, utxo, distances: [0] }); placedCircles.push({ x: 0, y: 0, r, utxo, i: index });
return; return;
} }
if (index === 1) { if (index === 1) {
const c = placedCircles[0]; const c = placedCircles[0];
placedCircles.push({ x: c.r + r, y: 0, r, utxo, distances: [c.r + r, 0] }); placedCircles.push({ x: c.r + r, y: 0, r, utxo, i: index });
c.distances.push(c.r + r); sortedInsert(positions, { c1: c, c2: placedCircles[1], d: c.r + r, p: 0 });
return;
}
if (index === 2) {
const c = placedCircles[0];
placedCircles.push({ x: -c.r - r, y: 0, r, utxo, i: index });
sortedInsert(positions, { c1: c, c2: placedCircles[2], d: c.r + r, p: 0 });
return; return;
} }
// The best position will be touching two other circles // The best position will be touching two other circles
// generate a list of candidate points by finding all such positions // find the closest such position to the center of the graph
// where the circle can be placed without overlapping other circles // where the circle can be placed without overlapping other circles
const candidates: [number, number, number[]][] = [];
const numCircles = placedCircles.length; const numCircles = placedCircles.length;
for (let i = 0; i < numCircles; i++) { let newCircle: UtxoCircle = null;
for (let j = i + 1; j < numCircles; j++) { while (positions.length > 0) {
const c1 = placedCircles[i]; const position = positions.shift();
const c2 = placedCircles[j]; // if the circles are too far apart, skip
if (c1.distances[j] > (c1.r + c2.r + r + r)) { if (position.d > (position.c1.r + position.c2.r + r + r)) {
// too far apart for new circle to touch both
continue; continue;
} }
const points = intersectionPoints(c1.x, c1.y, c1.r + r, c2.x, c2.y, c2.r + r);
points.forEach(([x, y]) => { const { x, y } = intersection(position.c1, position.c2, position.d, r, position.side);
const distances: number[] = []; if (isNaN(x) || isNaN(y)) {
// should never happen
continue;
}
// check if the circle would overlap any other circles here
let valid = true; let valid = true;
const nearbyCircles: { c: UtxoCircle, d: number, s: number }[] = [];
for (let k = 0; k < numCircles; k++) { for (let k = 0; k < numCircles; k++) {
const c = placedCircles[k]; const c = placedCircles[k];
if (k === position.c1.i || k === position.c2.i) {
nearbyCircles.push({ c, d: c.r + r, s: 0 });
continue;
}
const d = distance(x, y, c.x, c.y); const d = distance(x, y, c.x, c.y);
if (k !== i && k !== j && d < (r + c.r)) { if (d < (r + c.r)) {
valid = false; valid = false;
break; break;
} else { } else {
distances.push(d); nearbyCircles.push({ c, d, s: d - c.r - r });
} }
} }
if (valid) { if (valid) {
candidates.push([x, y, distances]); newCircle = { x, y, r, utxo, i: index };
} // add new positions to the candidate list
}); const nearest = nearbyCircles.sort((a, b) => a.s - b.s).slice(0, 5);
for (const n of nearest) {
if (n.d < (n.c.r + r + maxR + maxR)) {
sortedInsert(positions, { c1: newCircle, c2: n.c, d: n.d, p: distance((n.c.x + x) / 2, (n.c.y + y), 0, 0) });
} }
} }
break;
// Pick the candidate closest to the center of mass }
const [x, y, distances] = candidates.length ? candidates.reduce((closest, candidate) => }
distance(candidate[0], candidate[1], centerOfMass[0], centerOfMass[1]) < if (newCircle) {
distance(closest[0], closest[1], centerOfMass[0], centerOfMass[1]) placedCircles.push(newCircle);
? candidate } else {
: closest // should never happen
) : [0, 0, []]; return;
placedCircles.push({ x, y, r, utxo, distances });
for (let i = 0; i < distances.length; i++) {
placedCircles[i].distances.push(distances[i]);
} }
distances.push(0);
// Update center of mass
centerOfMass = {
x: (centerOfMass.x * weightOfMass + x) / (weightOfMass + r),
y: (centerOfMass.y * weightOfMass + y) / (weightOfMass + r),
};
weightOfMass += r;
}); });
// Precompute the bounding box of the graph // Precompute the bounding box of the graph
@ -165,23 +222,26 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
const width = maxX - minX; const width = maxX - minX;
const height = maxY - minY; const height = maxY - minY;
const data = placedCircles.map((circle, index) => [ const data = placedCircles.map((circle) => [
circle.utxo.txid + circle.utxo.vout,
circle.utxo, circle.utxo,
index,
circle.x, circle.x,
circle.y, circle.y,
circle.r circle.r,
]); ]);
this.chartOptions = { this.chartOptions = {
series: [{ series: [{
type: 'custom', type: 'custom',
coordinateSystem: undefined, coordinateSystem: undefined,
data, data: data,
encode: {
itemName: 0,
x: 2,
y: 3,
r: 4,
},
renderItem: (params, api) => { renderItem: (params, api) => {
const idx = params.dataIndex;
const datum = data[idx];
const utxo = datum[0] as Utxo;
const chartWidth = api.getWidth(); const chartWidth = api.getWidth();
const chartHeight = api.getHeight(); const chartHeight = api.getHeight();
const scale = Math.min(chartWidth / width, chartHeight / height); const scale = Math.min(chartWidth / width, chartHeight / height);
@ -189,34 +249,34 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
const scaledHeight = height * scale; const scaledHeight = height * scale;
const offsetX = (chartWidth - scaledWidth) / 2 - minX * scale; const offsetX = (chartWidth - scaledWidth) / 2 - minX * scale;
const offsetY = (chartHeight - scaledHeight) / 2 - minY * scale; const offsetY = (chartHeight - scaledHeight) / 2 - minY * scale;
const datum = data[params.dataIndex];
const utxo = datum[1] as Utxo;
const x = datum[2] as number; const x = datum[2] as number;
const y = datum[3] as number; const y = datum[3] as number;
const r = datum[4] as number; const r = datum[4] as number;
if (r * scale < 3) { if (r * scale < 2) {
// skip items too small to render cleanly // skip items too small to render cleanly
return; return;
} }
const valueStr = renderSats(utxo.value, this.stateService.network); const valueStr = renderSats(utxo.value, this.stateService.network);
const elements: any[] = [ const elements: any[] = [
{ {
type: 'circle', type: 'circle',
autoBatch: true, autoBatch: true,
shape: { shape: {
cx: (x * scale) + offsetX,
cy: (y * scale) + offsetY,
r: (r * scale) - 1, r: (r * scale) - 1,
}, },
style: { style: {
fill: '#5470c6', fill: '#' + this.getColor(utxo),
} }
}, },
]; ];
const labelFontSize = Math.min(36, r * scale * 0.25); const labelFontSize = Math.min(36, r * scale * 0.3);
if (labelFontSize > 8) { if (labelFontSize > 8) {
elements.push({ elements.push({
type: 'text', type: 'text',
x: (x * scale) + offsetX,
y: (y * scale) + offsetY,
style: { style: {
text: valueStr, text: valueStr,
fontSize: labelFontSize, fontSize: labelFontSize,
@ -228,9 +288,11 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
} }
return { return {
type: 'group', type: 'group',
x: (x * scale) + offsetX,
y: (y * scale) + offsetY,
children: elements, children: elements,
}; };
} },
}], }],
tooltip: { tooltip: {
backgroundColor: 'rgba(17, 19, 31, 1)', backgroundColor: 'rgba(17, 19, 31, 1)',
@ -242,27 +304,53 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
}, },
borderColor: '#000', borderColor: '#000',
formatter: (params: any): string => { formatter: (params: any): string => {
const utxo = params.data[0] as Utxo; const utxo = params.data[1] as Utxo;
const valueStr = renderSats(utxo.value, this.stateService.network); const valueStr = renderSats(utxo.value, this.stateService.network);
return ` return `
<b style="color: white;">${utxo.txid.slice(0, 6)}...${utxo.txid.slice(-6)}:${utxo.vout}</b> <b style="color: white;">${utxo.txid.slice(0, 6)}...${utxo.txid.slice(-6)}:${utxo.vout}</b>
<br> <br>
${valueStr}`; ${valueStr}
<br>
${utxo.status.confirmed ? 'Confirmed ' + this.timeService.calculate(utxo.status.block_time, 'since', true, 1, 'minute').text : 'Pending'}
`;
}, },
} }
}; };
this.lastUpdate = Date.now();
this.cd.markForCheck(); this.cd.markForCheck();
} }
onChartClick(e): void { getColor(utxo: Utxo): string {
if (e.data?.[0]?.txid) { if (utxo.status.confirmed) {
this.zone.run(() => { const age = Date.now() / 1000 - utxo.status.block_time;
const url = this.relativeUrlPipe.transform(`/tx/${e.data[0].txid}`); const oneHour = 60 * 60;
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) { const fourYears = 4 * 365 * 24 * 60 * 60;
window.open(url + '?mode=details#vout=' + e.data[0].vout);
if (age < oneHour) {
return newColorHex;
} else if (age >= fourYears) {
return oldColorHex;
} else { } else {
this.router.navigate([url], { fragment: `vout=${e.data[0].vout}` }); // Logarithmic scale between 1 hour and 4 years
const logAge = Math.log(age / oneHour);
const logMax = Math.log(fourYears / oneHour);
const t = logAge / logMax;
return colorToHex(mix(newColor, oldColor, t));
}
} else {
return pendingColorHex;
}
}
onChartClick(e): void {
if (e.data?.[1]?.txid) {
this.zone.run(() => {
const url = this.relativeUrlPipe.transform(`/tx/${e.data[1].txid}`);
if (e.event.event.shiftKey || e.event.event.ctrlKey || e.event.event.metaKey) {
window.open(url + '?mode=details#vout=' + e.data[1].vout);
} else {
this.router.navigate([url], { fragment: `vout=${e.data[1].vout}` });
} }
}); });
} }
@ -277,6 +365,7 @@ export class UtxoGraphComponent implements OnChanges, OnDestroy {
if (this.subscription) { if (this.subscription) {
this.subscription.unsubscribe(); this.subscription.unsubscribe();
} }
clearInterval(this.updateInterval);
} }
isMobile(): boolean { isMobile(): boolean {

View File

@ -9163,11 +9163,13 @@ export const restApiDocsData = [
Filters can be applied:<ul> Filters can be applied:<ul>
<li><code>status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code></li> <li><code>status</code>: <code>all</code>, <code>requested</code>, <code>accelerating</code>, <code>mined</code>, <code>completed</code>, <code>failed</code></li>
<li><code>timeframe</code>: <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>, <code>4y</code>, <code>all</code></li> <li><code>timeframe</code>: <code>24h</code>, <code>3d</code>, <code>1w</code>, <code>1m</code>, <code>3m</code>, <code>6m</code>, <code>1y</code>, <code>2y</code>, <code>3y</code>, <code>4y</code>, <code>all</code></li>
<li><code>poolUniqueId</code>: any id from <a target="_blank" href="https://github.com/mempool/mining-pools/blob/master/pools-v2.json">https://github.com/mempool/mining-pools/blob/master/pools-v2.json</a>. <i>Note: This will return all acceleration requests accepted by the pool but the the listed transactions may have been mined by another pool.</i> <li><code>minedByPoolUniqueId</code>: any id from <a target="_blank" href="https://github.com/mempool/mining-pools/blob/master/pools-v2.json">pools-v2.json</a>
<li><code>blockHash</code>: a block hash</a> <li><code>blockHash</code>: a block hash</a>
<li><code>blockHeight</code>: a block height</a> <li><code>blockHeight</code>: a block height</a>
<li><code>page</code>: the requested page number if using pagination <i>(min: 1)</i></a> <li><code>page</code>: the requested page number if using pagination <i>(min: 1)</i></a>
<li><code>pageLength</code>: the page lenght if using pagination <i>(min: 1, max: 50)</i></a> <li><code>pageLength</code>: the page lenght if using pagination <i>(min: 1, max: 50)</i></a>
<li><code>from</code>: unix timestamp (<i>overrides <code>timeframe</code></i>)</a>
<li><code>to</code>: unix timestamp (<i>overrides <code>timeframe</code></i>)</a>
</ul></p>` </ul></p>`
}, },
urlString: "/v1/services/accelerator/accelerations/history", urlString: "/v1/services/accelerator/accelerations/history",
@ -9187,21 +9189,22 @@ export const restApiDocsData = [
headers: '', headers: '',
response: `[ response: `[
{ {
"txid": "d7e1796d8eb4a09d4e6c174e36cfd852f1e6e6c9f7df4496339933cd32cbdd1d", "txid": "f829900985aad885c13fb90555d27514b05a338202c7ef5d694f4813ad474487",
"status": "completed", "status": "completed_provisional",
"added": 1707421053, "added": 1728111527,
"lastUpdated": 1719134667, "lastUpdated": 1728112113,
"effectiveFee": 146, "effectiveFee": 1385,
"effectiveVsize": 141, "effectiveVsize": 276,
"feeDelta": 14000, "feeDelta": 3000,
"blockHash": "00000000000000000000482f0746d62141694b9210a813b97eb8445780a32003", "blockHash": "00000000000000000000cde89e34036ece454ca2d07ddd7f71ab46307ca87423",
"blockHeight": 829559, "blockHeight": 864248,
"bidBoost": 3239, "bidBoost": 65,
"boostVersion": "v1", "boostVersion": "v2",
"pools": [ "pools": [
111 111,
115,
], ],
"minedByPoolUniqueId": 111 "minedByPoolUniqueId": 115
} }
]`, ]`,
}, },

View File

@ -203,6 +203,7 @@ export interface BlockExtension {
id: number; id: number;
name: string; name: string;
slug: string; slug: string;
minerNames: string[] | null;
} }
} }

View File

@ -165,7 +165,7 @@ export class ServicesApiServices {
return this.getAccelerationHistoryObserveResponse$({...params, page}).pipe( return this.getAccelerationHistoryObserveResponse$({...params, page}).pipe(
map((response) => ({ map((response) => ({
page, page,
total: parseInt(response.headers.get('X-Total-Count'), 10), total: parseInt(response.headers.get('X-Total-Count'), 10) || 0,
accelerations: accelerations.concat(response.body || []), accelerations: accelerations.concat(response.body || []),
})), })),
switchMap(({page, total, accelerations}) => { switchMap(({page, total, accelerations}) => {

View File

@ -0,0 +1,266 @@
import { Injectable } from '@angular/core';
import { DatePipe } from '@angular/common';
import { dates } from '../shared/i18n/dates';
const intervals = {
year: 31536000,
month: 2592000,
week: 604800,
day: 86400,
hour: 3600,
minute: 60,
second: 1
};
const precisionThresholds = {
year: 100,
month: 18,
week: 12,
day: 31,
hour: 48,
minute: 90,
second: 90
};
@Injectable({
providedIn: 'root'
})
export class TimeService {
constructor(private datePipe: DatePipe) {}
calculate(
time: number,
kind: 'plain' | 'since' | 'until' | 'span' | 'before' | 'within',
relative: boolean = false,
precision: number = 0,
minUnit: 'year' | 'month' | 'week' | 'day' | 'hour' | 'minute' | 'second' = 'second',
showTooltip: boolean = false,
units: string[] = ['year', 'month', 'week', 'day', 'hour', 'minute', 'second'],
dateString?: string,
lowercaseStart: boolean = false,
numUnits: number = 1,
fractionDigits: number = 0,
): { text: string, tooltip: string } {
if (time == null) {
return { text: '', tooltip: '' };
}
let seconds: number;
let tooltip: string = '';
switch (kind) {
case 'since':
seconds = Math.floor((+new Date() - +new Date(dateString || time * 1000)) / 1000);
tooltip = this.datePipe.transform(new Date(dateString || time * 1000), 'yyyy-MM-dd HH:mm') || '';
break;
case 'until':
case 'within':
seconds = (+new Date(time) - +new Date()) / 1000;
tooltip = this.datePipe.transform(new Date(time), 'yyyy-MM-dd HH:mm') || '';
break;
default:
seconds = Math.floor(time);
tooltip = '';
}
if (!showTooltip || relative) {
tooltip = '';
}
if (seconds < 1 && kind === 'span') {
return { tooltip, text: $localize`:@@date-base.immediately:Immediately` };
} else if (seconds < 60) {
if (relative || kind === 'since') {
if (lowercaseStart) {
return { tooltip, text: $localize`:@@date-base.just-now:Just now`.charAt(0).toLowerCase() + $localize`:@@date-base.just-now:Just now`.slice(1) };
}
return { tooltip, text: $localize`:@@date-base.just-now:Just now` };
} else if (kind === 'until' || kind === 'within') {
seconds = 60;
}
}
let counter: number;
const result: string[] = [];
let usedUnits = 0;
for (const [index, unit] of units.entries()) {
let precisionUnit = units[Math.min(units.length - 1, index + precision)];
counter = Math.floor(seconds / intervals[unit]);
const precisionCounter = Math.round(seconds / intervals[precisionUnit]);
if (precisionCounter > precisionThresholds[precisionUnit]) {
precisionUnit = unit;
}
if (units.indexOf(precisionUnit) === units.indexOf(minUnit)) {
counter = Math.max(1, counter);
}
if (counter > 0) {
let rounded;
const roundFactor = Math.pow(10,fractionDigits || 0);
if ((kind === 'until' || kind === 'within') && usedUnits < numUnits) {
rounded = Math.floor((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
} else {
rounded = Math.round((seconds / intervals[precisionUnit]) * roundFactor) / roundFactor;
}
if ((kind !== 'until' && kind !== 'within')|| numUnits === 1) {
return { tooltip, text: this.formatTime(kind, precisionUnit, rounded) };
} else {
if (!usedUnits) {
result.push(this.formatTime(kind, precisionUnit, rounded));
} else {
result.push(this.formatTime('', precisionUnit, rounded));
}
seconds -= (rounded * intervals[precisionUnit]);
usedUnits++;
if (usedUnits >= numUnits) {
return { tooltip, text: result.join(', ') };
}
}
}
}
return { tooltip, text: result.join(', ') };
}
private formatTime(kind, unit, number): string {
const dateStrings = dates(number);
switch (kind) {
case 'since':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-since:${dateStrings.i18nYear}:DATE: ago`; break;
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonth}:DATE: ago`; break;
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeek}:DATE: ago`; break;
case 'day': return $localize`:@@time-since:${dateStrings.i18nDay}:DATE: ago`; break;
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHour}:DATE: ago`; break;
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinute}:DATE: ago`; break;
case 'second': return $localize`:@@time-since:${dateStrings.i18nSecond}:DATE: ago`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-since:${dateStrings.i18nYears}:DATE: ago`; break;
case 'month': return $localize`:@@time-since:${dateStrings.i18nMonths}:DATE: ago`; break;
case 'week': return $localize`:@@time-since:${dateStrings.i18nWeeks}:DATE: ago`; break;
case 'day': return $localize`:@@time-since:${dateStrings.i18nDays}:DATE: ago`; break;
case 'hour': return $localize`:@@time-since:${dateStrings.i18nHours}:DATE: ago`; break;
case 'minute': return $localize`:@@time-since:${dateStrings.i18nMinutes}:DATE: ago`; break;
case 'second': return $localize`:@@time-since:${dateStrings.i18nSeconds}:DATE: ago`; break;
}
}
break;
case 'until':
if (number === 1) {
switch (unit) { // singular (In ~1 day)
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinute}:DATE:`;
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSecond}:DATE:`;
}
} else {
switch (unit) { // plural (In ~2 days)
case 'year': return $localize`:@@time-until:In ~${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-until:In ~${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-until:In ~${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-until:In ~${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-until:In ~${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-until:In ~${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-until:In ~${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'within':
if (number === 1) {
switch (unit) { // singular (In ~1 day)
case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinute}:DATE:`;
case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSecond}:DATE:`;
}
} else {
switch (unit) { // plural (In ~2 days)
case 'year': return $localize`:@@time-within:within ~${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-within:within ~${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-within:within ~${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-within:within ~${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-within:within ~${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-within:within ~${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-within:within ~${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'span':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYear}:DATE:`; break;
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonth}:DATE:`; break;
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeek}:DATE:`; break;
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDay}:DATE:`; break;
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHour}:DATE:`; break;
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinute}:DATE:`; break;
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSecond}:DATE:`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-span:After ${dateStrings.i18nYears}:DATE:`; break;
case 'month': return $localize`:@@time-span:After ${dateStrings.i18nMonths}:DATE:`; break;
case 'week': return $localize`:@@time-span:After ${dateStrings.i18nWeeks}:DATE:`; break;
case 'day': return $localize`:@@time-span:After ${dateStrings.i18nDays}:DATE:`; break;
case 'hour': return $localize`:@@time-span:After ${dateStrings.i18nHours}:DATE:`; break;
case 'minute': return $localize`:@@time-span:After ${dateStrings.i18nMinutes}:DATE:`; break;
case 'second': return $localize`:@@time-span:After ${dateStrings.i18nSeconds}:DATE:`; break;
}
}
break;
case 'before':
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return $localize`:@@time-before:${dateStrings.i18nYear}:DATE: before`; break;
case 'month': return $localize`:@@time-before:${dateStrings.i18nMonth}:DATE: before`; break;
case 'week': return $localize`:@@time-before:${dateStrings.i18nWeek}:DATE: before`; break;
case 'day': return $localize`:@@time-before:${dateStrings.i18nDay}:DATE: before`; break;
case 'hour': return $localize`:@@time-before:${dateStrings.i18nHour}:DATE: before`; break;
case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinute}:DATE: before`; break;
case 'second': return $localize`:@@time-before:${dateStrings.i18nSecond}:DATE: before`; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return $localize`:@@time-before:${dateStrings.i18nYears}:DATE: before`; break;
case 'month': return $localize`:@@time-before:${dateStrings.i18nMonths}:DATE: before`; break;
case 'week': return $localize`:@@time-before:${dateStrings.i18nWeeks}:DATE: before`; break;
case 'day': return $localize`:@@time-before:${dateStrings.i18nDays}:DATE: before`; break;
case 'hour': return $localize`:@@time-before:${dateStrings.i18nHours}:DATE: before`; break;
case 'minute': return $localize`:@@time-before:${dateStrings.i18nMinutes}:DATE: before`; break;
case 'second': return $localize`:@@time-before:${dateStrings.i18nSeconds}:DATE: before`; break;
}
}
break;
default:
if (number === 1) {
switch (unit) { // singular (1 day)
case 'year': return dateStrings.i18nYear; break;
case 'month': return dateStrings.i18nMonth; break;
case 'week': return dateStrings.i18nWeek; break;
case 'day': return dateStrings.i18nDay; break;
case 'hour': return dateStrings.i18nHour; break;
case 'minute': return dateStrings.i18nMinute; break;
case 'second': return dateStrings.i18nSecond; break;
}
} else {
switch (unit) { // plural (2 days)
case 'year': return dateStrings.i18nYears; break;
case 'month': return dateStrings.i18nMonths; break;
case 'week': return dateStrings.i18nWeeks; break;
case 'day': return dateStrings.i18nDays; break;
case 'hour': return dateStrings.i18nHours; break;
case 'minute': return dateStrings.i18nMinutes; break;
case 'second': return dateStrings.i18nSeconds; break;
}
}
}
return '';
}
}

View File

@ -204,12 +204,12 @@ export function renderSats(value: number, network: string, mode: 'sats' | 'btc'
break; break;
} }
if (mode === 'btc' || (mode === 'auto' && value >= 1000000)) { if (mode === 'btc' || (mode === 'auto' && value >= 1000000)) {
return `${amountShortenerPipe.transform(value / 100000000)} ${prefix}BTC`; return `${amountShortenerPipe.transform(value / 100000000, 2)} ${prefix}BTC`;
} else { } else {
if (prefix.length) { if (prefix.length) {
prefix += '-'; prefix += '-';
} }
return `${amountShortenerPipe.transform(value)} ${prefix}sats`; return `${amountShortenerPipe.transform(value, 2)} ${prefix}sats`;
} }
} }

View File

@ -8,7 +8,7 @@ export class BitcoinsatoshisPipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { } constructor(private sanitizer: DomSanitizer) { }
transform(value: string): SafeHtml { transform(value: string, firstPartClass?: string): SafeHtml {
const newValue = this.insertSpaces(parseFloat(value || '0').toFixed(8)); const newValue = this.insertSpaces(parseFloat(value || '0').toFixed(8));
const position = (newValue || '0').search(/[1-9]/); const position = (newValue || '0').search(/[1-9]/);
@ -16,7 +16,7 @@ export class BitcoinsatoshisPipe implements PipeTransform {
const secondPart = newValue.slice(position); const secondPart = newValue.slice(position);
return this.sanitizer.bypassSecurityTrustHtml( return this.sanitizer.bypassSecurityTrustHtml(
`<span class="text-secondary">${firstPart}</span>${secondPart}` `<span class="${firstPartClass ? firstPartClass : 'text-secondary'}">${firstPart}</span>${secondPart}`
); );
} }

View File

@ -365,6 +365,7 @@ import { OnlyVsizeDirective, OnlyWeightDirective } from './components/weight-dir
TwitterWidgetComponent, TwitterWidgetComponent,
TwitterLogin, TwitterLogin,
BitcoinInvoiceComponent, BitcoinInvoiceComponent,
BitcoinsatoshisPipe,
MempoolBlockOverviewComponent, MempoolBlockOverviewComponent,
ClockchainComponent, ClockchainComponent,

View File

@ -92,6 +92,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS'; set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
set $cors_origin 'https://mempool.space'; set $cors_origin 'https://mempool.space';
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'; set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
set $cors_expose_headers 'X-Total-Count';
set $cors_credentials 'true'; set $cors_credentials 'true';
# set CORS for approved hostnames # set CORS for approved hostnames
@ -100,6 +101,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS'; set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
set $cors_origin "$http_origin"; set $cors_origin "$http_origin";
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'; set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
set $cors_expose_headers 'X-Total-Count';
set $cors_credentials 'true'; set $cors_credentials 'true';
} }
@ -108,6 +110,7 @@ location @mempool-api-v1-services-cache-disabled-addcors {
add_header Access-Control-Allow-Origin "$cors_origin" always; add_header Access-Control-Allow-Origin "$cors_origin" always;
add_header Access-Control-Allow-Headers "$cors_headers" always; add_header Access-Control-Allow-Headers "$cors_headers" always;
add_header Access-Control-Allow-Credentials "$cors_credentials" always; add_header Access-Control-Allow-Credentials "$cors_credentials" always;
add_header Access-Control-Expose-Headers "$cors_expose_headers" always;
proxy_redirect off; proxy_redirect off;
proxy_buffering off; proxy_buffering off;
@ -172,6 +175,7 @@ location @mempool-api-v1-services-cache-short-addcors {
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS'; set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
set $cors_origin 'https://mempool.space'; set $cors_origin 'https://mempool.space';
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'; set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
set $cors_expose_headers 'X-Total-Count';
set $cors_credentials 'true'; set $cors_credentials 'true';
# set CORS for approved hostnames # set CORS for approved hostnames
@ -180,6 +184,7 @@ location @mempool-api-v1-services-cache-short-addcors {
set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS'; set $cors_methods 'GET, POST, PUT, DELETE, OPTIONS';
set $cors_origin "$http_origin"; set $cors_origin "$http_origin";
set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With'; set $cors_headers 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With';
set $cors_expose_headers 'X-Total-Count';
set $cors_credentials 'true'; set $cors_credentials 'true';
} }
@ -188,6 +193,7 @@ location @mempool-api-v1-services-cache-short-addcors {
add_header Access-Control-Allow-Origin "$cors_origin" always; add_header Access-Control-Allow-Origin "$cors_origin" always;
add_header Access-Control-Allow-Headers "$cors_headers" always; add_header Access-Control-Allow-Headers "$cors_headers" always;
add_header Access-Control-Allow-Credentials "$cors_credentials" always; add_header Access-Control-Allow-Credentials "$cors_credentials" always;
add_header Access-Control-Expose-Headers "$cors_expose_headers" always;
# add our own cache headers # add our own cache headers
add_header 'Pragma' 'public'; add_header 'Pragma' 'public';