Skip to content

Commit

Permalink
UIUX: Better node selection (#1987)
Browse files Browse the repository at this point in the history
  • Loading branch information
moneymanolis authored Dec 20, 2022
1 parent 1f09150 commit 4cf86fb
Show file tree
Hide file tree
Showing 14 changed files with 144 additions and 82 deletions.
4 changes: 2 additions & 2 deletions cypress/integration/spec_configures_nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ describe('Configuring nodes', () => {
cy.viewport(1200,660)
cy.visit('/')
cy.get('#node-switch-icon').click()
cy.get('#btn_new_node').click()
cy.get('[data-cy="connect-new-node-btn"]').click()
cy.get('[href="/nodes/new_node/"]').click()
cy.get('#name').clear()
cy.get('#name').type("Elements Node")
Expand Down Expand Up @@ -83,7 +83,7 @@ describe('Configuring nodes', () => {
cy.viewport(1200,660)
cy.visit('/')
cy.get('#node-switch-icon').click()
cy.get('#default-select-node-form > .item > div').click()
cy.contains('Bitcoin Core').click()
})

})
3 changes: 1 addition & 2 deletions cypress/integration/spec_elm_multi_segwit_wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ describe('Operating with an Elements multisig wallet', () => {
cy.viewport(1200,660)
cy.visit('/')
cy.get('#node-switch-icon').click()
cy.get('#elements_node-select-node-form > .item > div').click()
cy.get('[value="save"]').click()
cy.contains('Elements Node').click()

// Delete wallets if existing
cy.deleteWallet("Elm Multi Segwit Wallet")
Expand Down
2 changes: 1 addition & 1 deletion cypress/integration/spec_elm_single_segwit_wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ describe('Operating with an elements singlesig wallet', () => {
cy.viewport(1300,660)
cy.visit('/')
cy.get('#node-switch-icon').click()
cy.get('#elements_node-select-node-form > .item > div').click()
cy.contains('Elements Node').click()

// Delete Wallet if existing
cy.deleteWallet("Elm Single Segwit Hot Wallet")
Expand Down
10 changes: 5 additions & 5 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ idna==3.4 \
--hash=sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4 \
--hash=sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2
# via requests
importlib-metadata==4.8.1 \
importlib_metadata==4.8.1 \
--hash=sha256:b618b6d2d5ffa2f16add5697cf57a46c76a56229b0ed1c438322e4e95645bd15 \
--hash=sha256:f284b3e11256ad1e5d03ab86bb2ccd6f5339688ff17a4d797a0fe7df326f23b1
# via -r requirements.in
Expand Down Expand Up @@ -598,6 +598,10 @@ pytimeparse==1.1.8 \
--hash=sha256:04b7be6cc8bd9f5647a6325444926c3ac34ee6bc7e69da4367ba282f076036bd \
--hash=sha256:e86136477be924d7e670646a98561957e8ca7308d44841e21f5ddea757556a0a
# via -r requirements.in
pytz-deprecation-shim==0.1.0.post0 \
--hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \
--hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d
# via tzlocal
pytz==2022.5 \
--hash=sha256:335ab46900b1465e714b4fda4963d87363264eb662aab5e65da039c25f1f5b22 \
--hash=sha256:c4d88f472f54d615e9cd582a5004d1e5f624854a6a27a6211591c251f22a6914
Expand All @@ -606,10 +610,6 @@ pytz==2022.5 \
# babel
# flask-babel
# flask-restful
pytz-deprecation-shim==0.1.0.post0 \
--hash=sha256:8314c9692a636c8eb3bda879b9f119e350e93223ae83e70e80c31675a0fdc1a6 \
--hash=sha256:af097bae1b616dde5c5744441e2ddc69e74dfdcb0c263129610d85b87445a59d
# via tzlocal
requests==2.26.0 \
--hash=sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24 \
--hash=sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7
Expand Down
6 changes: 6 additions & 0 deletions src/cryptoadvance/specter/managers/node_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,12 @@ def get_by_name(self, name: str) -> Node:
return node
raise SpecterError("Node name %s does not exist!" % name)

def get_name_from_alias(self, alias: str) -> str:
for node in self.nodes.values():
if node.alias == alias:
return node.name
raise SpecterError("Node alias %s does not exist!" % alias)

def update_bitcoind_version(self, specter, version):
stopped_nodes = []
for node in (node for node in self.nodes.values() if not node.external_node):
Expand Down
4 changes: 3 additions & 1 deletion src/cryptoadvance/specter/server_endpoints/nodes.py
Original file line number Diff line number Diff line change
Expand Up @@ -372,5 +372,7 @@ def internal_node_logs(node_alias):
@login_required
def switch_node():
node_alias = request.form["node_alias"]
node_name = app.specter.node_manager.get_name_from_alias(node_alias)
app.specter.update_active_node(node_alias)
return redirect(url_for("nodes_endpoint.node_settings", node_alias=node_alias))
flash(_(f"Switched to use {node_name} as node."))
return redirect(url_for("index"))
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
18 changes: 11 additions & 7 deletions src/cryptoadvance/specter/static/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -277,25 +277,25 @@ a.item:hover{
opacity: 1;
}
/* disconnected node */
svg.core .main{
svg.core {
fill: var(--network-color, #79869B);
}
svg.core.test .main{
svg.core.test {
fill: var(--test-color);
}
svg.core.testnet .main{
svg.core.testnet {
fill: var(--test-color);
}
svg.core.main .main{
svg.core.main {
fill: var(--main-color);
}
svg.core.mainnet .main{
svg.core.mainnet {
fill: var(--main-color);
}
svg.core.regtest .main{
svg.core.regtest {
fill: var(--regtest-color);
}
svg.core.signet .main{
svg.core.signet {
fill: var(--signet-color);
}
small, .small{
Expand Down Expand Up @@ -1002,6 +1002,10 @@ table tr:hover .btn.hovering{
filter: invert(100%) sepia(0%) saturate(1%) hue-rotate(304deg) brightness(102%) contrast(101%);
}

.svg-gray {
filter: invert(75%) sepia(0%) saturate(1%) hue-rotate(304deg) brightness(102%) contrast(101%);
}

/************** Mobile styles ********************************/
#side-expand{
display: none;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
- chain: Chain to use to colorize the Bitcoin icon
- size: Svg width and height
#}
{% macro bitcoin_svg(chain, size) -%}
<svg class="core {{ chain }}" height="{{ size }}" width="{{ size }}" viewBox="0 0 64 64" style="opacity: 0.95;">
{% macro bitcoin_svg(node, size) -%}
<svg class="core {{ node.chain }}" height="{{ size }}" width="{{ size }}" viewBox="0 0 64 64" style="opacity: 0.95;">
<g>
<path class="main" d="m63.033,39.744c-4.274,17.143-21.637,27.576-38.782,23.301-17.138-4.274-27.571-21.638-23.295-38.78,4.272-17.145,21.635-27.579,38.775-23.305,17.144,4.274,27.576,21.64,23.302,38.784z"/>
<path fill="#ffff" d="m46.103,27.444c0.637-4.258-2.605-6.547-7.038-8.074l1.438-5.768-3.511-0.875-1.4,5.616c-0.923-0.23-1.871-0.447-2.813-0.662l1.41-5.653-3.509-0.875-1.439,5.766c-0.764-0.174-1.514-0.346-2.242-0.527l0.004-0.018-4.842-1.209-0.934,3.75s2.605,0.597,2.55,0.634c1.422,0.355,1.679,1.296,1.636,2.042l-1.638,6.571c0.098,0.025,0.225,0.061,0.365,0.117-0.117-0.029-0.242-0.061-0.371-0.092l-2.296,9.205c-0.174,0.432-0.615,1.08-1.609,0.834,0.035,0.051-2.552-0.637-2.552-0.637l-1.743,4.019,4.569,1.139c0.85,0.213,1.683,0.436,2.503,0.646l-1.453,5.834,3.507,0.875,1.439-5.772c0.958,0.26,1.888,0.5,2.798,0.726l-1.434,5.745,3.511,0.875,1.453-5.823c5.987,1.133,10.489,0.676,12.384-4.739,1.527-4.36-0.076-6.875-3.226-8.515,2.294-0.529,4.022-2.038,4.483-5.155zm-8.022,11.249c-1.085,4.36-8.426,2.003-10.806,1.412l1.928-7.729c2.38,0.594,10.012,1.77,8.878,6.317zm1.086-11.312c-0.99,3.966-7.1,1.951-9.082,1.457l1.748-7.01c1.982,0.494,8.365,1.416,7.334,5.553z"/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
- chain: Chain to use to colorize the Bitcoin icon
- size: Svg width and height
#}
{% macro liquid_svg(chain, size) -%}
<svg class="core {{ chain }}" height="{{ size }}" width="{{ size }}" viewBox="0 0 46 46" style="opacity: 0.95;">
{% macro liquid_svg(node, size) -%}
<svg class="core {{ node.chain }}" height="{{ size }}" width="{{ size }}" viewBox="0 0 46 46" style="opacity: 0.95;">
<style type="text/css">
.st0{fill:#46B4A5;}
.st1{fill:#FFFFFF;}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,35 +1,21 @@
{% if specter.node.is_running %}
{% set node = specter.node %}
{% if node.is_running %}
<div id="active-node" onclick="showPageOverlay('bitcoin_core_info');document.getElementById('side-content').classList.remove('active');" class="item core" style="cursor: pointer;">

{% if specter.is_liquid %}
{% from "components/liquid_svg.jinja" import liquid_svg %}
{{ liquid_svg(specter.info.chain, 40) }}
{% else %}
{% from "components/bitcoin_svg.jinja" import bitcoin_svg %}
{{ bitcoin_svg(specter.info.chain, 40) }}
{% endif %}
{% include node.node_logo_template() %}
{% include "includes/sidebar/components/bitcoin_core_info.jinja" %}
<div>
<span style="max-width: 140px;">{{ specter.node.name }}</span>
<span style="max-width: 140px;">{{ node.name }}</span> <br>
<small>
{% if specter.chain %}
{% if specter.bitcoin_core_version != '' %}
v{{ specter.bitcoin_core_version }}
{% endif %}
({{specter.chain}}):
{% if specter.info["blocks"] %}
{{ specter.info.blocks }} blocks
{% else %}
{{ specter.info.chain }}
{% endif %}
{{node.chain | title}} node
{% endif %}
</small>
</div>
</div>
{% else %}
<a id="no-node-connection" class="no-connection" href="{{ url_for('nodes_endpoint.node_settings', node_alias=specter.node.alias) }}">
<a id="no-node-connection" class="no-connection" href="{{ url_for('nodes_endpoint.node_settings', node_alias=node.alias) }}">
<div style="display: flex; gap: 5px">
<span>{{ _("No node connection") }}</span>
<span>{{ _("No Bitcoin Core connection") }}</span>
<img src="{{ url_for('static', filename='img/broken-connection.svg') }}" style="width: 15px;">
</div>
<div id="configure-click" class="configure-click">{{ _("Click to configure") }}</div>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{% if specter.node.is_liquid %}
{% from "components/liquid_svg.jinja" import liquid_svg %}
{{ liquid_svg(specter.node.chain, 40) }}
{% if node.is_liquid %}
{% from "components/liquid_svg.jinja" import liquid_svg %}
{{ liquid_svg(node.chain, 40) }}
{% else %}
{% from "components/bitcoin_svg.jinja" import bitcoin_svg %}
{{ bitcoin_svg(specter.node.chain, 40) }}
{% from "components/bitcoin_svg.jinja" import bitcoin_svg %}
{{ bitcoin_svg(node, 40) }}
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -4,72 +4,135 @@
justify-content: flex-start;
flex-grow: 1;
width: 100%;
padding: 10px 0;
padding: 10px;
display: flex;
flex-direction: row;
align-items: center;
border-left: 3px solid transparent;
border-left: 3px solid transparent;
width: 275px;
height: 60px;
cursor: pointer;
border: 1px solid transparent;
border-radius: 10px;
}
.node-list-item-selected {
border: 1px solid orange;
border-radius: 10px;
cursor: unset;
}
.node-list-item>svg {
width: 80px;
}
.node-list-item.active{
background: #394659;
}
@media (min-width: 600px){
#node_select_popup {
width: 350px;
}
}
.page_overlay_popup {
padding: '1.5em 0';
text-align: unset;
}
.node-select-popup-container {
flex-direction: column;
align-items: start;
gap: 10px;
}
.selected-icon {
width: 20px
}
.node-logo-unreachable {
margin-right: 15px;
opacity: 0.1;
}
.node-logo-running {
margin-right: 15px;
opacity: 1;
}
.node-description {
display: flex;
flex-direction: column;
}
.node-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: space-between;
}
.node-name {
color: white
}
.node-list-item:hover {
border: 1px dotted orange;
border-radius: 10px;
}
.node-list-item-selected:hover {
cursor: default;
border: 1px solid orange;
border-radius: 10px;
}
</style>
<div id="node_select_popup" class="hidden" style="text-align: left;">
<h1>{{ _("Node configuration") }}</h1>

<div id="node_select_popup" class="hidden">
<h1 style="align-self: center">{{ _("Node selection") }}</h1>
{% for node_name in specter.node_manager.nodes_names %}
{% set node = specter.node_manager.get_by_name(node_name) %}
<form action="{{url_for('nodes_endpoint.switch_node')}}" id="{{node.alias}}-select-node-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="node_alias" value="{{ node.alias }}"/>
<div class="item core node-list-item {% if node.alias == specter.node.alias %}active{% endif %}" style="cursor: pointer;"
onclick="document.getElementById('{{node.alias}}-select-node-form').submit();">
{% include node.node_logo_template() %}
<div style="width: 100%;">
{{ node.name }}
<br>
<small>
{% if node.chain %}
{{node.chain | title}}, {{ node.host }}:{{ node.port }}
{% else %}
{{ _("Node unreachable...") }}
{% endif %}
</small>
{% set selected_node = node.alias == specter.node.alias %}
{% set running = node.is_running %}
<div id="node-row" class="node-row">
<form action="{{url_for('nodes_endpoint.switch_node')}}" id="{{node.alias}}-select-node-form" method="POST">
<input type="hidden" class="csrf-token" name="csrf_token" value="{{ csrf_token() }}"/>
<input type="hidden" name="node_alias" value="{{ node.alias }}"/>
<div {% if selected_node %} class="core node-list-item node-list-item-selected" {% else %} class="core node-list-item" {% endif %} onclick="document.getElementById('{{node.alias}}-select-node-form').submit();">
<div id="node-logo" {% if running %} class="node-logo-running" {% else %} class="node-logo-unreachable" {% endif %}>
{% include node.node_logo_template() %}
</div>
<div id="node-description" class="node-description" {% if selected_node %} title="Selected node" {% else %} title="Select node" {% endif%}>
<div style="display: flex; gap: 10px">
<div class="node-name">{{ node.name }}</div>
{% if running %}
<img title="Node is running" class="selected-icon"src="{{ url_for('static', filename='img/radio-button-running-node.svg')}}">
{% endif %}
</div>
<small>
{% if running %}
{{node.chain | title}} node
{% else %}
<span style="color: grey; font-size: 12px">Node unreachable</span>
{% endif %}
</small>
</div>
</div>
<a href="{{ url_for('nodes_endpoint.node_settings', node_alias=node.alias) }}" style="text-align: right; width: 100%px;">
<img src="{{ url_for('static', filename='img/gear.svg')}}" class="svg-white" style="width: 40px; margin-right: 10px;"/>
</a>
</div>
</form>
</form>
<a title="Configure node" href="{{ url_for('nodes_endpoint.node_settings', node_alias=node.alias) }}">
<img src="{{ url_for('static', filename='img/gear.svg')}}" class="svg-gray" style="width: 40px; margin-right: 10px;"/>
</a>
</div>
{% endfor %}
<br>
{{ sidebar_btn(url_for('setup_endpoint.node_type'), 'Connect a new node', 'btn_new_node') }}
<div style="align-self: center; margin-top: 10px">
<a href="{{ url_for('setup_endpoint.node_type') }}" class="btn" data-cy="connect-new-node-btn">Connect a new node</a>
</div>
</div>

<script type="text/javascript">
function showNodeSelectPopup() {
// Show node selection popup when clicking on the arrows on the sidebar
hidePageOverlay();
if (!e) var e = window.event;
e.cancelBubble = true;
if (e.stopPropagation) {
e.stopPropagation();
}
document.getElementById('page_overlay_popup').style.padding = '1.5em 0';
document.getElementById('page_overlay_popup_cancel_button').classList.add('hidden');
document.getElementById('side-content').classList.remove('active');
showPageOverlay('node_select_popup');
// Change the default styling of the node selection popup
let pageOverlayPopup = document.getElementById('page_overlay_popup')
pageOverlayPopup.classList.add('page_overlay_popup')
let nodeSelectPopupContainer = document.getElementById('node_select_popup');
nodeSelectPopupContainer.style.display = 'flex'
nodeSelectPopupContainer.classList.add('node-select-popup-container')
}
function onCancelOverlay() {
document.getElementById('page_overlay_popup').style.padding = '1.5em';
document.getElementById('page_overlay_popup_cancel_button').classList.remove('hidden');
}
</script>

0 comments on commit 4cf86fb

Please sign in to comment.