mirror of
https://github.com/cryptoadvance/specter-desktop.git
synced 2026-08-13 12:33:29 +02:00
Feature: Rescan button for empty wallet (#1779)
* Prompting rescan hint within tx-table when no transactions appear * Animation of rescan part after being forwarded * Basics option added in test-cypress for faster development on cypress tests on slower machines * addWallet command added in Cypress * Page limit in addresses view fixed Co-authored-by: moneymanolis <moneymanolis@protonmail.com>
This commit is contained in:
parent
51255fb9f7
commit
a3ce7d3186
21 changed files with 279 additions and 125 deletions
|
|
@ -6,6 +6,7 @@
|
|||
"spec_configures_nodes.js",
|
||||
"spec_devices.js",
|
||||
"spec_ghost_machine.js",
|
||||
"spec_rescan.js",
|
||||
"spec_qr_signing.js",
|
||||
"spec_wallet_send.js",
|
||||
"spec_wallet_utxo.js",
|
||||
|
|
|
|||
|
|
@ -1,15 +1,26 @@
|
|||
describe('Test adding different devices', () => {
|
||||
before(() => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
Cypress.config('includeShadowDom', true)
|
||||
})
|
||||
|
||||
// Keeps the session cookie alive, Cypress by default clears all cookies before each test
|
||||
beforeEach(() => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
Cypress.Cookies.preserveOnce('session')
|
||||
})
|
||||
|
||||
it('Filter devices', () => {
|
||||
cy.get('#toggle_devices_list').click()
|
||||
cy.get('#btn_new_device').click()
|
||||
cy.contains('Select Your Device Type')
|
||||
cy.get('#device-type-searchbar').type("Specter")
|
||||
cy.get('#trezor_device_card').should('not.be.visible')
|
||||
cy.get('#specter_device_card').should('be.visible')
|
||||
cy.get('#specter_device_card').click()
|
||||
cy.contains('Connect your Specter-DIY')
|
||||
})
|
||||
|
||||
it('Electrum device', () => {
|
||||
cy.get('#toggle_devices_list').click()
|
||||
cy.get('#btn_new_device').click()
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
|
||||
describe('Completely empty specter-home', () => {
|
||||
beforeEach(() => {
|
||||
before(() => {
|
||||
cy.task("clear:specter-home")
|
||||
})
|
||||
it('Visits specter and clicks around', () => {
|
||||
|
||||
it('Click around on the welcome page', () => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
cy.visit('/welcome/about')
|
||||
cy.contains('Welcome to Specter Desktop')
|
||||
cy.get('#node-switch-icon').click()
|
||||
cy.get('[href="/nodes/node/default/"]').first().click()
|
||||
|
|
@ -16,28 +17,16 @@ describe('Completely empty specter-home', () => {
|
|||
cy.contains('Authentication:')
|
||||
cy.get('[href="/settings/hwi"]').click()
|
||||
cy.contains('Hardware Devices Bridge')
|
||||
// Hidden in Cypress behind the price
|
||||
// cy.get('[href="/settings/tor"]').click()
|
||||
// cy.contains('Tor configurations')
|
||||
cy.get('main').scrollTo('top')
|
||||
cy.contains('Tor').click({ scrollBehavior: false })
|
||||
cy.contains('Tor configurations')
|
||||
cy.contains("Choose plugins")
|
||||
cy.get('#btn_plugins').click()
|
||||
cy.contains("Plugins in Production")
|
||||
|
||||
|
||||
})
|
||||
|
||||
it('Creates a device in Specter', () => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
cy.addDevice("Some Device")
|
||||
})
|
||||
|
||||
it('Dummytest to enforce remove of device', () => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -2,26 +2,7 @@ describe('Ghost machine', () => {
|
|||
it('Create a DIY device with ghost machine keys and a wallet', () => {
|
||||
cy.viewport(1200,660)
|
||||
cy.visit('/')
|
||||
// addDevice is creating a DIY device with ghost machine keys
|
||||
cy.addDevice("DIY ghost")
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes('Wallet ghost')) {
|
||||
cy.contains('Wallet ghost').click()
|
||||
cy.get('#btn_settings').click({"force": true})
|
||||
cy.get('#advanced_settings_tab_btn').click()
|
||||
cy.get('#delete_wallet').click()
|
||||
}
|
||||
})
|
||||
cy.get('#btn_new_wallet').click()
|
||||
cy.get('[href="./simple/"]').click()
|
||||
cy.get('#diy_ghost').click()
|
||||
cy.get('#wallet_name').type("Wallet ghost")
|
||||
cy.get('#keysform > .centered').click()
|
||||
cy.get('body').contains("New wallet was created successfully!")
|
||||
// Download PDF
|
||||
// unfortunately this results in weird effects in cypress run
|
||||
//cy.get('#pdf-wallet-download > img').click()
|
||||
cy.get('#btn_continue').click()
|
||||
cy.mine2wallet("btc")
|
||||
cy.addDevice('DIY ghost', 'Specter-DIY', 'ghost_machine')
|
||||
cy.addWallet('Ghost wallet', null, 'segwit', 'funded', 'btc')
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ describe('Test plugins', () => {
|
|||
|
||||
it('Associate an address with a service', () => {
|
||||
// choose address
|
||||
cy.selectWallet("Wallet ghost")
|
||||
cy.selectWallet("Ghost wallet")
|
||||
cy.get('main').contains('Addresses').click()
|
||||
// Click on the first address
|
||||
cy.contains('td', '#0').siblings().contains('bcrt').click()
|
||||
|
|
@ -24,7 +24,7 @@ describe('Test plugins', () => {
|
|||
cy.contains("Admin password successfully updated")
|
||||
|
||||
// Choose address again
|
||||
cy.selectWallet("Wallet ghost")
|
||||
cy.selectWallet("Ghost wallet")
|
||||
cy.get('main').contains('Addresses').click()
|
||||
cy.contains('td', '#0').siblings().contains('bcrt').click()
|
||||
cy.get('#associate-btn').click()
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ describe('Test QR code signing flow', () => {
|
|||
})
|
||||
|
||||
it('Message signing with Specter DIY', () => {
|
||||
cy.get('.side').contains('Wallet ghost').click()
|
||||
cy.get('.side').contains('Ghost wallet').click()
|
||||
cy.get('main').contains('Addresses').click()
|
||||
// Click on the first address
|
||||
cy.contains('td', '#0').siblings().contains('bcrt').click()
|
||||
|
|
@ -33,7 +33,7 @@ describe('Test QR code signing flow', () => {
|
|||
cy.get('#diy_ghost_qr_sign_msg_btn').should('not.exist')
|
||||
cy.get('#diy_ghost_usb_sign_msg_btn').should('exist')
|
||||
cy.get('#page_overlay_popup_cancel_button').click()
|
||||
cy.contains("Wallet ghost").click()
|
||||
cy.contains("Ghost wallet").click()
|
||||
cy.get('main').contains('Addresses').click()
|
||||
cy.contains('td', '#0').siblings().contains('bcrt').click()
|
||||
cy.get('#msg-signing-btn').should('exist')
|
||||
|
|
|
|||
29
cypress/integration/spec_rescan.js
Normal file
29
cypress/integration/spec_rescan.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
describe('Test the UI related to a blockchain rescan', () => {
|
||||
before(() => {
|
||||
Cypress.config('includeShadowDom', true)
|
||||
})
|
||||
|
||||
// Keeps the session cookie alive, Cypress by default clears all cookies before each test
|
||||
beforeEach(() => {
|
||||
cy.visit('/')
|
||||
cy.viewport(1200,660)
|
||||
Cypress.Cookies.preserveOnce('session')
|
||||
})
|
||||
|
||||
it('Go to the rescan section from a fresh wallet', () => {
|
||||
// Create a completely fresh wallet which is not receiving funds from the continous mining
|
||||
cy.addDevice('Trezor hold', 'Trezor', 'hold_accident')
|
||||
cy.addWallet('Fresh wallet', 'Trezor hold', 'segwit', false)
|
||||
cy.get('#btn_transactions').click()
|
||||
cy.get('#go-to-rescan-btn').click()
|
||||
cy.get('#blockchain-rescan').should('be.visible')
|
||||
// TODO: Do we keep this wallet and this device or teardown?
|
||||
})
|
||||
|
||||
it('Check that there is no button for a used wallet', () => {
|
||||
// Only works if the ghost wallet was created with funded option
|
||||
cy.selectWallet('Ghost wallet')
|
||||
cy.get('#rescan-hint > p').should('not.be.visible')
|
||||
cy.get('#rescan-hint > .btn').should('not.be.visible')
|
||||
})
|
||||
})
|
||||
|
|
@ -98,7 +98,7 @@ describe('Test the actions in UTXO list', () => {
|
|||
|
||||
it('Managing unsigned transactions', () => {
|
||||
// Make an unsigned tx
|
||||
cy.get('#address_0').type("bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs") // address from "Wallet ghost"
|
||||
cy.get('#address_0').type("bcrt1qvtdx75y4554ngrq6aff3xdqnvjhmct5wck95qs") // address from "Ghost wallet"
|
||||
cy.get('#send_max_0').click()
|
||||
cy.get('#create_psbt_btn').click()
|
||||
// Check the labeling of the unsigned UTXO
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@
|
|||
|
||||
import 'cypress-wait-until';
|
||||
|
||||
Cypress.Commands.add("addDevice", (name) => {
|
||||
Cypress.Commands.add("addDevice", (name, device_type, mnemonic) => {
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes(name)) {
|
||||
cy.get('#toggle_devices_list').click()
|
||||
|
|
@ -38,25 +38,23 @@ Cypress.Commands.add("addDevice", (name) => {
|
|||
cy.get('#toggle_devices_list').click()
|
||||
}
|
||||
cy.get('#btn_new_device').click()
|
||||
// Creating a Device
|
||||
cy.contains('Select Your Device Type')
|
||||
cy.get('#trezor_device_card')
|
||||
cy.get('#device-type-searchbar').type("specter")
|
||||
cy.contains('Select Your Device Type')
|
||||
cy.get('#trezor_device_card').should('not.have.class', 'disabled')
|
||||
cy.get('#specter_device_card').click()
|
||||
cy.get('h2 > input').type(name)
|
||||
cy.go('back')
|
||||
cy.get('#device-type-container > .note').click()
|
||||
cy.contains('Manual configuration').click()
|
||||
cy.get('#device_name').type(name)
|
||||
cy.get('#device_type').select("Specter-DIY")
|
||||
// Device types are the names to select from, such as Trezor, Specter-DIY or Electrum
|
||||
cy.get('#device_type').select(device_type)
|
||||
if (mnemonic === "ghost_machine" || mnemonic === null) {
|
||||
cy.get('#txt').type("[8c24a510/84h/1h/0h]vpub5Y24kG7ZrCFRkRnHia2sdnt5N7MmsrNry1jMrP8XptMEcZZqkjQA6bc1f52RGiEoJmdy1Vk9Qck9tAL1ohKvuq3oFXe3ADVse6UiTHzuyKx")
|
||||
cy.get('#txt').type("\n[8c24a510/49h/1h/0h]upub5DCn7wm4SgVmzmtdoi8DVVfxhBJkqL1L6mmKHNgVky1Fj5VyBxV6NzKD957sr5fWXkY5y8THtqSVWWpjLnomBYw4iXpxaPbkXg5Gn6s5tQf")
|
||||
cy.get('#txt').type("\n[8c24a510/48h/1h/0h/1h]Upub5S2BXfT5rv2bc2i4Hr8NaBzcu243ztEMJ7LUDK4A9UKRtVmr9EFNdNdPz8rAXQnZDeAoHA8KcR7grVjREWKpBr69bev4rkvxytLZ6fN3sUv")
|
||||
cy.get('#txt').type("\n[8c24a510/48h/1h/0h/2h]Vpub5krSqL811ba5VJdUoP42TFmfRkAaR6h4uxdDThCvDd24PR5gXWPHCvASLbEKevdQQjGx3i1WG7ueEARb8Hpo2u4HikY3wnvwvF1VSakkjew")
|
||||
cy.get('#cold_device > [type="submit"]').click()
|
||||
cy.get('#toggle_devices_list').click()
|
||||
cy.get('#devices_list > .item > div').contains(name)
|
||||
}
|
||||
if (mnemonic === "hold_accident") {
|
||||
cy.get('#txt').type("[ccf2e5c3/84h/1h/0h]vpub5YkPJgRQsev79YZM1NRDKJWDjLFcD2xSFAt6LehC5iiMMqQgMHyCFQzwsu16Rx9rBpXZVXPjWAxybuCpsayaw8qCDZtjwH9vifJ7WiQkHwu")
|
||||
cy.get('#txt').type("\n[ccf2e5c3/49h/1h/0h]upub5DH3pJxqyFKA9Xu8mbKie67UvJ5VWsDDtEg2YR98Yy99UGFNnBa6VSk36zW1ZWTrbYa1Nk6zrxSvzL2hdzjbRUatmwaVUPPYzyEniauECJy")
|
||||
cy.get('#txt').type("\n[ccf2e5c3/48h/1h/0h/1h]Upub5RyWnpxermQY5L7knm9gDPMQGcFKqat9pDKxhNGoVQHFDubgitAESzyS6QH65ebd7KCs6njXEL1kh1iCweiodWT1xtq69VNx2Cwog97WEDt")
|
||||
cy.get('#txt').type("\n[ccf2e5c3/48h/1h/0h/2h]Vpub5kon6Vda1Sx1xfPziprUxjBU9PjxGisfosRz3yvWz9odArUVF4embnoB4rEN76CVc5r1UB5JYXxzHTSQS6M1mAob8Wsw6FXk57RubBaizov")
|
||||
}
|
||||
cy.contains("Continue").click()
|
||||
})
|
||||
})
|
||||
|
||||
|
|
@ -151,6 +149,40 @@ Cypress.Commands.add("addHotWallet", (wallet_name, device_name, node_type, walle
|
|||
})
|
||||
})
|
||||
|
||||
Cypress.Commands.add("addWallet", (wallet_name, device_name, wallet_type, funded, node_type) => {
|
||||
if (wallet_type == null) {
|
||||
wallet_type = "segwit"
|
||||
}
|
||||
if (device_name == null) {
|
||||
device_name = "DIY ghost"
|
||||
}
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes(wallet_name)) {
|
||||
cy.contains(wallet_name).click()
|
||||
cy.get('#btn_settings' ).click( {force: true})
|
||||
cy.get('#advanced_settings_tab_btn').click()
|
||||
cy.get('#delete_wallet').click()
|
||||
}
|
||||
cy.get('#side-content').click()
|
||||
cy.get('#btn_new_wallet').click()
|
||||
cy.get('[href="./simple/"]').click()
|
||||
var device_button = "#"+device_name.toLowerCase().replace(/ /g,"_")
|
||||
cy.get(device_button).click()
|
||||
cy.get('#wallet_name').type(wallet_name)
|
||||
if (wallet_type == "nested_segwit") {
|
||||
cy.get('#type_nested_segwit_btn').click()
|
||||
}
|
||||
if (wallet_type == "taproot") {
|
||||
cy.get('#type_taproot_btn').click()
|
||||
}
|
||||
cy.get('#keysform > .centered').click()
|
||||
cy.get('body').contains("New wallet was created successfully!")
|
||||
cy.get('#btn_continue').click()
|
||||
if (funded) {
|
||||
cy.mine2wallet(node_type)
|
||||
}
|
||||
})
|
||||
})
|
||||
Cypress.Commands.add("deleteWallet", (name) => {
|
||||
cy.get('body').then(($body) => {
|
||||
if ($body.text().includes(name)) {
|
||||
|
|
|
|||
11
cypress_basics.json
Normal file
11
cypress_basics.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"testFiles": [
|
||||
"spec_configures_nodes.js",
|
||||
"spec_ghost_machine.js"
|
||||
],
|
||||
"baseUrl": "http://localhost:25444",
|
||||
"env": {
|
||||
"broadcast_timeout":"8000"
|
||||
},
|
||||
"includeShadowDom": false
|
||||
}
|
||||
|
|
@ -444,18 +444,18 @@ def miner_loop(node_impl, my_node, data_folder, mining_every_x_seconds, echo):
|
|||
|
||||
|
||||
def mine_2_specter_wallets(node_impl, my_node, data_folder, echo):
|
||||
"""Get each specter-wallet some coins"""
|
||||
"""Sending coins to all Specter wallets, except for "Fresh wallet" which is not supposed to have any tx"""
|
||||
|
||||
from ..process_controller.node_controller import fetch_wallet_addresses_for_mining
|
||||
|
||||
# Using the dict key, not the wallet name
|
||||
exception = "fresh_wallet"
|
||||
try:
|
||||
|
||||
for address in fetch_wallet_addresses_for_mining(node_impl, data_folder):
|
||||
echo("")
|
||||
for address in fetch_wallet_addresses_for_mining(
|
||||
node_impl, data_folder, exception
|
||||
):
|
||||
echo(f"Mining to address {address}")
|
||||
my_node.testcoin_faucet(address)
|
||||
# my_node.mine(address=address)
|
||||
# my_node.mine(block_count=100)
|
||||
except FileNotFoundError:
|
||||
# might happen if there no ~/.specter folder yet
|
||||
# might happen if there is no ~/.specter folder yet
|
||||
pass
|
||||
|
|
|
|||
|
|
@ -555,9 +555,10 @@ def find_node_executable(node_impl):
|
|||
return which(f"{node_impl}d")
|
||||
|
||||
|
||||
def fetch_wallet_addresses_for_mining(node_impl, data_folder):
|
||||
"""parses all the wallet-jsons in the folder (default ~/.specter/wallets/regtest)
|
||||
and returns an array with the addresses
|
||||
def fetch_wallet_addresses_for_mining(node_impl, data_folder, exception=None):
|
||||
"""
|
||||
Parses all the wallet jsons in the folder (default ~/.specter/wallets/regtest) and returns an array with the addresses.
|
||||
Pass a wallet name via the exception argument so that this wallet's addresses are not included.
|
||||
"""
|
||||
print(f"{data_folder}/wallets")
|
||||
print(os.listdir(f"{data_folder}"))
|
||||
|
|
@ -569,6 +570,11 @@ def fetch_wallet_addresses_for_mining(node_impl, data_folder):
|
|||
f"{data_folder}/{folder}/{'regtest' if node_impl == 'bitcoin' else 'elreg'}"
|
||||
)
|
||||
wallets = load_jsons(wallet_folder)
|
||||
if exception and exception in wallets:
|
||||
print(
|
||||
f"Deleting wallet {exception} from wallets that receive faucet coins in testing."
|
||||
)
|
||||
del wallets[exception]
|
||||
address_array = [value["address"] for key, value in wallets.items()]
|
||||
# remove duplicates
|
||||
address_array = list(dict.fromkeys(address_array))
|
||||
|
|
|
|||
|
|
@ -828,6 +828,7 @@ def settings(wallet_alias):
|
|||
else:
|
||||
app.specter.wallet_manager.rename_wallet(wallet, wallet_name)
|
||||
|
||||
scroll_to_rescan_blockchain = request.args.get("rescan_blockchain")
|
||||
return render_template(
|
||||
"wallet/settings/wallet_settings.jinja",
|
||||
purposes=purposes,
|
||||
|
|
@ -836,4 +837,5 @@ def settings(wallet_alias):
|
|||
specter=app.specter,
|
||||
rand=rand,
|
||||
error=error,
|
||||
scroll_to_rescan_blockchain=scroll_to_rescan_blockchain,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -345,7 +345,7 @@ nav .separator{
|
|||
background: var(--cmap-border);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 4px;
|
||||
padding: 8px 15px 8px 10px;
|
||||
padding: 8px 10px 8px 10px;
|
||||
color: #fff;
|
||||
font-size: 0.85em;
|
||||
text-align: center;
|
||||
|
|
@ -742,7 +742,7 @@ th{
|
|||
font-weight: inherit;
|
||||
border-bottom: 3px solid var(--cmap-bg-lightest);
|
||||
}
|
||||
tbody tr:hover{
|
||||
tbody tr:not(.nohover):hover{
|
||||
background: rgba(255,255,255,0.03);
|
||||
color: #fff;
|
||||
}
|
||||
|
|
@ -832,12 +832,6 @@ table a:hover, .address-link:hover{
|
|||
color: #fff;
|
||||
text-decoration: underline;
|
||||
}
|
||||
table .btn{
|
||||
margin: -10px 0;
|
||||
padding: 5px 10px;
|
||||
width: auto;
|
||||
min-width: 0;
|
||||
}
|
||||
table .btn.hovering{
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
<a class="pagination-next pagination-arrow pagination-disabled">›</a>
|
||||
<br>
|
||||
</div>
|
||||
<div>
|
||||
<div style="padding-left: 5px">
|
||||
{% include "includes/page-limit-select.html" %}
|
||||
</div>
|
||||
<div class="export-container hidden">
|
||||
|
|
|
|||
|
|
@ -1,8 +1,5 @@
|
|||
<div style="display: flex; align-items: center;">
|
||||
<style>
|
||||
.page-limit-label {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
.page-limit-fieldset {
|
||||
border: none;
|
||||
margin-top: 4px; /*to better vertically align on tx history / utxo list overview*/
|
||||
|
|
|
|||
|
|
@ -216,6 +216,35 @@
|
|||
input:checked + .slider {
|
||||
background-color: hsl(217, 30%, 50%);
|
||||
}
|
||||
.feedback-row {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.rescan-hint {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
font-size: 1.1em;
|
||||
}
|
||||
.go-to-rescan-btn {
|
||||
max-width: 175px;
|
||||
margin: 0 0 5px 0;
|
||||
border: 1px solid var(--cmap-border);
|
||||
border-radius: 0.5em;
|
||||
background-color: var(--cmap-border);
|
||||
padding: 0.8em;
|
||||
text-align: center;
|
||||
font-weight: 250;
|
||||
}
|
||||
.go-to-rescan-btn:hover {
|
||||
background-color: var(--cmap-border);
|
||||
border: 1px solid var(--main-color);
|
||||
text-decoration: none;
|
||||
}
|
||||
.empty-row {
|
||||
display: table-row;
|
||||
text-align: center;
|
||||
}
|
||||
:host {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
|
@ -309,7 +338,7 @@
|
|||
</div>
|
||||
<div id="table-container" class="table-container">
|
||||
<table class="tx-table">
|
||||
<thead>
|
||||
<thead id="table-header">
|
||||
<tr>
|
||||
<th value="callbackaction" class="column-callbackaction customaction-header"></th>
|
||||
<th value="category" class="column-category category-header"><div class="category-arrow"></div></th>
|
||||
|
|
@ -329,16 +358,16 @@
|
|||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr class="empty">
|
||||
<td class="column-callbackaction"></td>
|
||||
<td class="column-category"></td>
|
||||
<td class="column-txid">{{ _("Fetching transactions...") }}</td>
|
||||
<td class="column-label optional"></td>
|
||||
<td class="column-amount optional"></td>
|
||||
<td class="column-time" ></td>
|
||||
<td class="column-confirmations"></td>
|
||||
<td class="column-blockhash optional hidden blockhash-empty"></td>
|
||||
<td class="column-action"></td>
|
||||
<tr id="feedback-row" class="hidden nohover">
|
||||
<td id="feedback-row-content">
|
||||
<div id="rescan-hint" class="hidden">
|
||||
<p>{{_('Is this not a fresh wallet? Did you expect transactions here? Then click') }} 👇 {{_('to find them') }}</p>
|
||||
<a id="go-to-rescan-btn" href="{{ url_for('wallets_endpoint.settings', wallet_alias=wallet_alias, rescan_blockchain=True) }}" class="go-to-rescan-btn btn">Go to blockchain rescan</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr id="empty-row" class="hidden nohover">
|
||||
<td id="empty-row-cell" colspan="9"></td>
|
||||
</tr>
|
||||
<tr class="summary-row hidden">
|
||||
<td class="column-callbackaction"></td>
|
||||
|
|
@ -366,11 +395,18 @@
|
|||
var style = document.getElementById('tx-table').content;
|
||||
var clone = style.cloneNode(true);
|
||||
this.el = clone.querySelector(".tx-table");
|
||||
this.tableHeader = clone.getElementById('table-header')
|
||||
this.tbody = clone.querySelector(".tx-tbody");
|
||||
this.tableContainer = clone.getElementById("table-container")
|
||||
|
||||
// Empty row element
|
||||
this.emptyRow = clone.querySelector(".empty");
|
||||
// Feedback row
|
||||
this.feedbackRow = clone.getElementById('feedback-row');
|
||||
this.feedbackRowContent = clone.getElementById('feedback-row-content')
|
||||
this.rescanHint = clone.getElementById('rescan-hint')
|
||||
|
||||
// Empty row
|
||||
this.emptyRow = clone.getElementById('empty-row')
|
||||
this.emptyRowCell = clone.getElementById('empty-row-cell')
|
||||
|
||||
// Blockhash header and empty column
|
||||
this.blockhashHeader = clone.querySelector(".blockhash-header");
|
||||
|
|
@ -699,11 +735,9 @@
|
|||
if (attrName != "selected-coins") {
|
||||
if (this.getAttribute('blockhash') == 'true' && this.getAttribute('type') == "txlist" && this.getAttribute('wallet')) {
|
||||
this.blockhashHeader.classList.remove('hidden');
|
||||
this.blockhashEmpty.classList.remove('hidden');
|
||||
this.blockhashSummary.classList.remove('hidden');
|
||||
} else {
|
||||
this.blockhashHeader.classList.add('hidden');
|
||||
this.blockhashEmpty.classList.add('hidden');
|
||||
this.blockhashSummary.classList.add('hidden');
|
||||
}
|
||||
if (
|
||||
|
|
@ -808,8 +842,11 @@
|
|||
}
|
||||
break
|
||||
default:
|
||||
this.export.href = ``
|
||||
this.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }} ';
|
||||
this.export.href = ``;
|
||||
this.tableHeader.classList.add('hidden');
|
||||
this.feedbackRow.classList.remove('hidden');
|
||||
this.feedbackRow.classList.add('feedback-row');
|
||||
this.feedbackRowContent.innerText = '{{ _("Failed to fetch transactions ...") }} ';
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -828,7 +865,9 @@
|
|||
formData.append('service_id', this.serviceId);
|
||||
}
|
||||
formData.append('csrf_token', '{{ csrf_token() }}');
|
||||
console.log(url)
|
||||
this.emptyRow.classList.remove('hidden');
|
||||
this.emptyRow.classList.add('empty-row');
|
||||
this.emptyRowCell.innerText = '{{ _("Fetching transactions ...") }}';
|
||||
try {
|
||||
let callId = this.callId;
|
||||
const response = await fetch(
|
||||
|
|
@ -865,13 +904,16 @@
|
|||
// "txlist": "[{\"txid\": \"1f2b4f9b6762ab37d3b0495cda51d8733c68a5e3a01356f0a67f215a29b2f05e\", \"blockhash\": \"05d7c1f7c4fa2a530e0916dcd15551657c22f4c30df175292b0c0b0f320adc10\", \"blockheight\": 722, \"time\": 1644162928, \"conflicts\": [], \"bip125-replaceable\": \"no\", \"vsize\": null, \"category\": \"receive\", \"address\": \"bcrt1qc3z4j7kksr3wm2heaahzx62yjrj8fn83s7x3ps\", \"amount\": 20.0, \"ismine\": true, \"confirmations\": 132, \"label\": \"Address #7\", \"validated_blockhash\": \"\", \"wallet_alias\": \"myhot_2\"}, {\"txid\": \"aafaecb085944a6fce4556885c1f10c1cd97bac146a2a0e676a2b12bcf428d3b\", \"blockhash\": \"201eea173347cdc8e8effeaf96053b5867860cec163f3eacec7c8918c94bdaaa\", \"blockheight\": 721, \"time\": 1644162928, \"conflicts\": [], \"bip125-replaceable\": \"no\", \"vsize\": null, \"category\": \"receive\", \"address\": \"bcrt1qyncynwdxmeynk245wa3f4urul3nm2v0ulfueek\", \"amount\": 20.0, \"ismine\": true, \"confirmations\": 133, \"label\": \"Address #6\", \"validated_blockhash\": \"\", \"wallet_alias\": \"myhot_2\"}, {\"txid\": \"bf34b090996dfcd2971c88f645edd4a0a0ad180e15096389d8841cf2bac0ecde\", \"blockhash\": \"07952e41edea8a9507722531e922b9d217518d6116ba4fba47c9e62ce12dee9e\", \"blockheight\": 305, \"time\": 1644159720, \"conflicts\": [], \"bip125-replaceable\": \"no\", \"vsize\": null, \"category\": \"receive\", \"address\": \"bcrt1qyncynwdxmeynk245wa3f4urul3nm2v0ulfueek\", \"amount\": 20.0, \"ismine\": true, \"confirmations\": 549, \"label\": \"Address #6\", \"validated_blockhash\": \"\", \"wallet_alias\": \"myhot_2\"}]"
|
||||
// }
|
||||
|
||||
this.emptyRow.classList.remove('empty-row');
|
||||
this.emptyRow.classList.add('hidden');
|
||||
// Clean up from existing transaction list
|
||||
this.summaryRow.classList.add('hidden');
|
||||
this.emptyRow.children[1].innerText = '{{ _("Fetching transactions...") }}';
|
||||
this.emptyRow.classList.remove('hidden');
|
||||
this.tbody.querySelectorAll('tx-row').forEach((row) => {row.remove()});
|
||||
if (!this.listType) {
|
||||
this.emptyRow.children[1].innerText = '{{ _("Failed to fetch transactions...") }}';
|
||||
this.tableHeader.classList.add('hidden');
|
||||
this.feedbackRow.classList.remove('hidden');
|
||||
this.feedbackRow.classList.add('feedback-row');
|
||||
this.feedbackRow.innerText = '{{ _("Failed to fetch transactions ...") }}';
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -880,7 +922,6 @@
|
|||
} else {
|
||||
this.utxoViewBtn.classList.add("checked");
|
||||
}
|
||||
|
||||
// Populate the table with TXRowElement objects using the fetched txlist
|
||||
if (jsonResponse.txlist) {
|
||||
let txlist = JSON.parse(jsonResponse.txlist);
|
||||
|
|
@ -917,13 +958,19 @@
|
|||
}
|
||||
// Pagination
|
||||
this.pageCount = jsonResponse.pageCount;
|
||||
// Show empty row if no transaction found
|
||||
|
||||
// Show rescan hint if no transactions are found
|
||||
if (this.pageCount == 0 || txlist.length == 0) {
|
||||
this.emptyRow.classList.remove('hidden');
|
||||
this.emptyRow.children[1].innerText = '{{ _("No transactions found...") }}';
|
||||
this.tableHeader.classList.add('hidden');
|
||||
this.feedbackRow.classList.add('feedback-row');
|
||||
this.rescanHint.classList.remove('hidden')
|
||||
this.rescanHint.classList.add('rescan-hint')
|
||||
this.pageCount = 1;
|
||||
} else {
|
||||
this.emptyRow.classList.add('hidden');
|
||||
this.tableHeader.classList.remove('hidden');
|
||||
this.feedbackRow.classList.add('hidden');
|
||||
this.rescanHint.classList.remove('rescan-hint')
|
||||
this.rescanHint.classList.add('hidden');
|
||||
}
|
||||
|
||||
// If index is greater than the pages count fetch again for the last page
|
||||
|
|
|
|||
|
|
@ -22,3 +22,5 @@
|
|||
</tx-table>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -12,6 +12,14 @@
|
|||
border-top: 1px solid #aaa;
|
||||
margin: 40px 20px;
|
||||
}
|
||||
#blockchain-rescan {
|
||||
transition: border-color 3s;
|
||||
}
|
||||
.animation-rescan {
|
||||
padding-top: 5px;
|
||||
border: 0.5px solid orange;
|
||||
border-radius: 10px;
|
||||
}
|
||||
</style>
|
||||
<br>
|
||||
<nav class="row collapse-on-mobile">
|
||||
|
|
@ -91,7 +99,7 @@
|
|||
{% endif %}
|
||||
{% endfor %}
|
||||
{% if supports_export_to_device != [] %}
|
||||
<h2 class="subtitle">{{ _("Export wallet to device") }}</h1>
|
||||
<h2 class="subtitle">{{ _("Export wallet to device") }}</h2>
|
||||
<p class="center">
|
||||
{{ _("Export this wallet to a device by scanning its QR code or importing its data file.") }}
|
||||
</p>
|
||||
|
|
@ -220,7 +228,7 @@
|
|||
</div>
|
||||
</form>
|
||||
<div class="section-separator"></div>
|
||||
<h2 class="subtitle">{{ _("Blockchain rescan") }}</h2>
|
||||
<h2 class="subtitle" id="blockchain-rescan">{{ _("Blockchain rescan") }}</h2>
|
||||
<form action="." method="POST" class="padded" style="max-width: 500px">
|
||||
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
|
||||
{% if wallet.rescan_progress %}
|
||||
|
|
@ -230,7 +238,7 @@
|
|||
</div>
|
||||
{% else %}
|
||||
{{ _("Rescan blockchain from block:") }}<br>
|
||||
<div class="row aligned">
|
||||
<div class="row aligned" style="justify-content: space-between;">
|
||||
{% if specter.info.chain == "main" %}
|
||||
{% if wallet.is_taproot %}
|
||||
{% set startblock=(709632 if not specter.info['pruned'] or specter.info['pruneheight'] < 709632 else specter.info['pruneheight']) %}
|
||||
|
|
@ -240,16 +248,16 @@
|
|||
{% else %}
|
||||
{% set startblock=(0 if not specter.info['pruned'] else specter.info['pruneheight']) %}
|
||||
{% endif %}
|
||||
<input type="number" name="startblock" value="{{startblock}}" style="margin-right: 10px" min="{{startblock}}">
|
||||
<button type="submit" name="action" value="rescanblockchain" class="btn">{{ _("Rescan") }}</button>
|
||||
<input type="number" name="startblock" value="{{startblock}}" style="flex-basis: 220px;" min="{{startblock}}">
|
||||
<button id="rescan-btn" type="submit" name="action" value="rescanblockchain" class="btn" style="flex-basis: 170px; flex-grow: 0;">{{ _("Rescan") }}</button>
|
||||
</div>
|
||||
{% if specter.info['pruned'] %}
|
||||
<div class="note center full-width">{{ _("Note: You are using a pruned node. Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}</div>
|
||||
<div class="note full-width">{{ _("Note: You are using a pruned node. Rescan is limited by the pruned height to block:") }} {{specter.info['pruneheight']}}</div>
|
||||
{% endif %}
|
||||
{% if wallet.is_taproot %}
|
||||
<div class="note center"><b>709632</b> {{ _("was the first Taproot block.") }}</div>
|
||||
<div class="note"><b>709632</b> {{ _("was the first Taproot block.") }}</div>
|
||||
{% else %}
|
||||
<div class="note center"><b>481824</b> {{ _("was the first Segwit block.") }}</div>
|
||||
<div class="note"><b>481824</b> {{ _("was the first Segwit block.") }}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</form>
|
||||
|
|
@ -261,10 +269,10 @@
|
|||
<button type="submit" name="action" value="abortrescanutxo" class="btn" style="max-width: 130px; margin-left: 20px">{{ _("Abort rescan") }}</button>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="row aligned">
|
||||
<div class="row aligned" style="justify-content: space-between;">
|
||||
<div style="flex-grow: 1">{{ _("Or rescan only unspent transactions:") }}
|
||||
</div>
|
||||
<button type="submit" name="action" value="rescanutxo" class="btn" style="max-width: 180px; margin-left: 20px">
|
||||
<button id="rescan-utxo-btn" type="submit" name="action" value="rescanutxo" class="btn" style="flex-grow: 0; max-width: 170px; max-height: 35.5px">
|
||||
{{ _("Rescan UTXO") }}
|
||||
<div class="tool-tip">
|
||||
<i class="tool-tip__icon">i</i>
|
||||
|
|
@ -433,6 +441,16 @@
|
|||
let textarea = document.getElementById("address_labels_data");
|
||||
textarea.value = "";
|
||||
})
|
||||
|
||||
function scrolltoRescanBlockchain() {
|
||||
setSettingsView("advanced");
|
||||
document.getElementById('blockchain-rescan').scrollIntoView();
|
||||
}
|
||||
// Only called when the user comes from an empty tx list to rescan
|
||||
{% if scroll_to_rescan_blockchain %}
|
||||
scrolltoRescanBlockchain();
|
||||
document.getElementById('blockchain-rescan').classList.add("animation-rescan")
|
||||
{% endif %}
|
||||
</script>
|
||||
|
||||
{% endblock %}
|
||||
|
|
|
|||
26
utils/cypress_basics.py
Executable file
26
utils/cypress_basics.py
Executable file
|
|
@ -0,0 +1,26 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Returns a string of a list of basic test files (such as configuring the nodes) on which other more specialised tests depend on.
|
||||
This string is picked up by a subprocess in test-cypress.sh
|
||||
"""
|
||||
|
||||
import json
|
||||
import click
|
||||
import logging
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@click.command()
|
||||
def execute():
|
||||
with open("cypress_basics.json") as json_file:
|
||||
data = json.load(json_file)
|
||||
spec_run_list = []
|
||||
for file in data["testFiles"]:
|
||||
spec_run_list.append("./cypress/integration/" + file)
|
||||
click.echo(",".join(spec_run_list))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
execute()
|
||||
|
|
@ -337,6 +337,14 @@ function sub_run {
|
|||
fi
|
||||
}
|
||||
|
||||
function sub_basics {
|
||||
start_bitcoind --reset
|
||||
start_elementsd --reset
|
||||
start_specter --reset
|
||||
$(npm bin)/cypress run --spec $(./utils/cypress_basics.py) --config video=false
|
||||
$(npm bin)/cypress open
|
||||
}
|
||||
|
||||
function sub_snapshot {
|
||||
spec_file=$1
|
||||
# We'll create a snapshot BEFORE this spec-file has been tested:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue