mirror of
https://github.com/ACINQ/eclair.git
synced 2025-02-22 14:22:39 +01:00
Readibility improvements in GUI (#801)
* Improved amounts readability (fixes #542) and added the Bits unit denomination in the documentation * Improved channel panel UI layout * Added a confirmation dialog when closing a channel, with a short summary of the channel to be closed The balance bar has been shrunk in size so that it should not be mistaken as a separator element between channels. The channel's balance, capacity and peer node id are now more visible. Also added the short channel id to the channel's pane. fixes #690 * Added node's aggregated balance in the status bar fixes #775
This commit is contained in:
parent
10ea7bdc23
commit
e3b2992934
12 changed files with 311 additions and 192 deletions
|
@ -103,7 +103,7 @@ name | description
|
|||
eclair.bitcoind.rpcpassword | Bitcoin Core RPC password | bar
|
||||
eclair.bitcoind.zmqblock | Bitcoin Core ZMQ block address | "tcp://127.0.0.1:29000"
|
||||
eclair.bitcoind.zmqtx | Bitcoin Core ZMQ tx address | "tcp://127.0.0.1:29000"
|
||||
eclair.gui.unit | Unit in which amounts are displayed (possible values: msat, sat, mbtc, btc) | btc
|
||||
eclair.gui.unit | Unit in which amounts are displayed (possible values: msat, sat, bits, mbtc, btc) | btc
|
||||
|
||||
Quotes are not required unless the value contains special characters. Full syntax guide [here](https://github.com/lightbend/config/blob/master/HOCON.md).
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
|
||||
package fr.acinq.eclair
|
||||
|
||||
import java.text.DecimalFormat
|
||||
import java.text.{DecimalFormat, NumberFormat}
|
||||
|
||||
import fr.acinq.bitcoin.{Btc, BtcAmount, MilliBtc, MilliSatoshi, Satoshi}
|
||||
import grizzled.slf4j.Logging
|
||||
|
@ -93,19 +93,44 @@ case object BtcUnit extends CoinUnit {
|
|||
|
||||
object CoinUtils extends Logging {
|
||||
|
||||
val COIN_PATTERN = "###,###,###,##0.###########"
|
||||
var COIN_FORMAT = new DecimalFormat(COIN_PATTERN)
|
||||
// msat pattern, no decimals allowed
|
||||
val MILLI_SAT_PATTERN = "#,###,###,###,###,###,##0"
|
||||
|
||||
def setCoinPattern(pattern: String) = {
|
||||
// sat pattern decimals are optional
|
||||
val SAT_PATTERN = "#,###,###,###,###,##0.###"
|
||||
|
||||
// bits pattern always shows 2 decimals (msat optional)
|
||||
val BITS_PATTERN = "##,###,###,###,##0.00###"
|
||||
|
||||
// milli btc pattern always shows 5 decimals (msat optional)
|
||||
val MILLI_BTC_PATTERN = "##,###,###,##0.00000###"
|
||||
|
||||
// btc pattern always shows 8 decimals (msat optional). This is the default pattern.
|
||||
val BTC_PATTERN = "##,###,##0.00000000###"
|
||||
|
||||
var COIN_FORMAT: NumberFormat = new DecimalFormat(BTC_PATTERN)
|
||||
|
||||
def setCoinPattern(pattern: String): Unit = {
|
||||
COIN_FORMAT = new DecimalFormat(pattern)
|
||||
}
|
||||
|
||||
def getPatternFromUnit(unit: CoinUnit): String = {
|
||||
unit match {
|
||||
case MSatUnit => MILLI_SAT_PATTERN
|
||||
case SatUnit => SAT_PATTERN
|
||||
case BitUnit => BITS_PATTERN
|
||||
case MBtcUnit => MILLI_BTC_PATTERN
|
||||
case BtcUnit => BTC_PATTERN
|
||||
case _ => throw new IllegalArgumentException("unhandled unit")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a string amount denominated in a bitcoin unit to a Millisatoshi amount. The amount might be truncated if
|
||||
* it has too many decimals because MilliSatoshi only accepts Long amount.
|
||||
*
|
||||
* @param amount numeric String, can be decimal.
|
||||
* @param unit bitcoin unit, can be milliSatoshi, Satoshi, milliBTC, BTC.
|
||||
* @param unit bitcoin unit, can be milliSatoshi, Satoshi, Bits, milliBTC, BTC.
|
||||
* @return amount as a MilliSatoshi object.
|
||||
* @throws NumberFormatException if the amount parameter is not numeric.
|
||||
* @throws IllegalArgumentException if the unit is not equals to milliSatoshi, Satoshi or milliBTC.
|
||||
|
@ -132,7 +157,7 @@ object CoinUtils extends Logging {
|
|||
fr.acinq.bitcoin.millisatoshi2satoshi(CoinUtils.convertStringAmountToMsat(amount, unit))
|
||||
|
||||
/**
|
||||
* Only BtcUnit, MBtcUnit, SatUnit and MSatUnit codes or label are supported.
|
||||
* Only BtcUnit, MBtcUnit, BitUnit, SatUnit and MSatUnit codes or label are supported.
|
||||
* @param unit
|
||||
* @return
|
||||
*/
|
||||
|
@ -199,13 +224,13 @@ object CoinUtils extends Logging {
|
|||
}
|
||||
|
||||
/**
|
||||
* Converts the amount to the user preferred unit and returns the Long value.
|
||||
* Converts the amount to the user preferred unit and returns the BigDecimal value.
|
||||
* This method is useful to feed numeric text input without formatting.
|
||||
*
|
||||
* Returns -1 if the given amount can not be converted.
|
||||
*
|
||||
* @param amount BtcAmount
|
||||
* @return Long value of the BtcAmount
|
||||
* @return BigDecimal value of the BtcAmount
|
||||
*/
|
||||
def rawAmountInUnit(amount: BtcAmount, unit: CoinUnit): BigDecimal = Try(convertAmountToGUIUnit(amount, unit) match {
|
||||
case a: BtcAmountGUILossless => BigDecimal(a.amount_msat) / a.unit.factorToMsat
|
||||
|
|
|
@ -27,6 +27,8 @@ class CoinUtilsSpec extends FunSuite {
|
|||
assert(am_btc == MilliSatoshi(100000000000L))
|
||||
val am_mbtc: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MBtcUnit.code)
|
||||
assert(am_mbtc == MilliSatoshi(100000000L))
|
||||
val am_bits: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", BitUnit.code)
|
||||
assert(am_bits == MilliSatoshi(100000))
|
||||
val am_sat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", SatUnit.code)
|
||||
assert(am_sat == MilliSatoshi(1000))
|
||||
val am_msat: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1", MSatUnit.code)
|
||||
|
@ -42,6 +44,8 @@ class CoinUtilsSpec extends FunSuite {
|
|||
assert(am_mbtc_dec_nozero == MilliSatoshi(25000000L))
|
||||
val am_mbtc_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", MBtcUnit.code)
|
||||
assert(am_mbtc_dec == MilliSatoshi(123456789L))
|
||||
val am_bits_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", BitUnit.code)
|
||||
assert(am_bits_dec == MilliSatoshi(123456))
|
||||
val am_sat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.23456789", SatUnit.code)
|
||||
assert(am_sat_dec == MilliSatoshi(1234))
|
||||
val am_msat_dec: MilliSatoshi = CoinUtils.convertStringAmountToMsat("1.234", MSatUnit.code)
|
||||
|
@ -80,19 +84,23 @@ class CoinUtilsSpec extends FunSuite {
|
|||
assert(CoinUtils.rawAmountInUnit(MilliSatoshi(123), SatUnit) == BigDecimal(0.123))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliSatoshi(123), MSatUnit) == BigDecimal(123))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliSatoshi(12345678), BtcUnit) == BigDecimal(0.00012345678))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliSatoshi(1234567), BitUnit) == BigDecimal(12.34567))
|
||||
|
||||
assert(CoinUtils.rawAmountInUnit(Satoshi(123), BtcUnit) == BigDecimal(0.00000123))
|
||||
assert(CoinUtils.rawAmountInUnit(Satoshi(123), MBtcUnit) == BigDecimal(0.00123))
|
||||
assert(CoinUtils.rawAmountInUnit(Satoshi(123), BitUnit) == BigDecimal(1.23))
|
||||
assert(CoinUtils.rawAmountInUnit(Satoshi(123), SatUnit) == BigDecimal(123))
|
||||
assert(CoinUtils.rawAmountInUnit(Satoshi(123), MSatUnit) == BigDecimal(123000))
|
||||
|
||||
assert(CoinUtils.rawAmountInUnit(MilliBtc(123.456), BtcUnit) == BigDecimal(0.123456))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliBtc(123.456), MBtcUnit) == BigDecimal(123.456))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliBtc(123.45678), BitUnit) == BigDecimal(123456.78))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliBtc(123.456789), SatUnit) == BigDecimal(12345678.9))
|
||||
assert(CoinUtils.rawAmountInUnit(MilliBtc(123.45678987), MSatUnit) == BigDecimal(12345678987L))
|
||||
|
||||
assert(CoinUtils.rawAmountInUnit(Btc(123.456), BtcUnit) == BigDecimal(123.456))
|
||||
assert(CoinUtils.rawAmountInUnit(Btc(123.45678987654), MBtcUnit) == BigDecimal(123456.78987654))
|
||||
assert(CoinUtils.rawAmountInUnit(Btc(123.456789876), BitUnit) == BigDecimal(123456789.876))
|
||||
assert(CoinUtils.rawAmountInUnit(Btc(1.22233333444), SatUnit) == BigDecimal(122233333.444))
|
||||
assert(CoinUtils.rawAmountInUnit(Btc(0.00011111222), MSatUnit) == BigDecimal(11111222L))
|
||||
}
|
||||
|
|
|
@ -15,10 +15,26 @@
|
|||
-fx-font-weight: bold;
|
||||
}
|
||||
|
||||
.text-xs {
|
||||
-fx-font-size: 10px;
|
||||
}
|
||||
|
||||
.text-sm {
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.text-md {
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
.text-lg {
|
||||
-fx-font-size: 16px;
|
||||
}
|
||||
|
||||
.text-xl {
|
||||
-fx-font-size: 18px;
|
||||
}
|
||||
|
||||
.text-error {
|
||||
-fx-text-fill: rgb(216,31,74);
|
||||
-fx-font-size: 11px;
|
||||
|
@ -115,15 +131,13 @@
|
|||
/* ---------- Progress Bar ---------- */
|
||||
|
||||
.bar {
|
||||
-fx-background-color: rgb(63,179,234);
|
||||
-fx-background-color: rgb(114,193,229);
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
|
||||
.track {
|
||||
-fx-background-color: rgb(206,230,255);
|
||||
-fx-background-color: rgb(211,227,234);
|
||||
-fx-background-insets: 0;
|
||||
-fx-background-radius: 0;
|
||||
}
|
||||
|
||||
/* ---------- Forms ----------- */
|
||||
|
|
|
@ -1,8 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.ProgressBar?>
|
||||
<?import javafx.scene.control.TextField?>
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
<?import javafx.scene.layout.HBox?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2018 ACINQ SAS
|
||||
~
|
||||
|
@ -19,67 +28,66 @@
|
|||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<VBox fx:id="root" styleClass="channel" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1"
|
||||
onContextMenuRequested="#openChannelContext">
|
||||
<VBox fx:id="root" onContextMenuRequested="#openChannelContext" styleClass="channel" xmlns="http://javafx.com/javafx/8.0.172-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@./main.css"/>
|
||||
<URL value="@../commons/globals.css" />
|
||||
<URL value="@./main.css" />
|
||||
</stylesheets>
|
||||
<GridPane styleClass="grid" prefWidth="400.0">
|
||||
<GridPane prefWidth="600.0" styleClass="grid">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="140.0" prefWidth="150.0" maxWidth="180.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="30.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="40.0" prefWidth="60.0" maxWidth="60.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="30.0" prefWidth="40.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="380.0" minWidth="100.0" prefWidth="100.0" />
|
||||
<ColumnConstraints hgrow="NEVER" maxWidth="1.0" minWidth="1.0" prefWidth="1.0" />
|
||||
<ColumnConstraints hgrow="NEVER" maxWidth="90.0" minWidth="90.0" prefWidth="90.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" prefWidth="100.0" />
|
||||
<ColumnConstraints hgrow="NEVER" maxWidth="50.0" minWidth="50.0" prefWidth="50.0" />
|
||||
<ColumnConstraints hgrow="NEVER" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="4.0" vgrow="SOMETIMES" />
|
||||
</rowConstraints>
|
||||
|
||||
<HBox GridPane.columnSpan="4" GridPane.columnIndex="0" alignment="CENTER" spacing="5.0">
|
||||
<TextField fx:id="channelId" text="N/A" editable="false" styleClass="noteditable, text-strong"
|
||||
HBox.hgrow="ALWAYS" GridPane.valignment="BOTTOM" focusTraversable="false"/>
|
||||
<HBox GridPane.columnIndex="4" GridPane.halignment="RIGHT" alignment="CENTER_RIGHT" HBox.hgrow="NEVER" spacing="5">
|
||||
<Button fx:id="close" mnemonicParsing="false" styleClass="close-channel" text="Close" visible="false"/>
|
||||
<Button fx:id="forceclose" mnemonicParsing="false" styleClass="forceclose-channel" text="Force close" visible="false"/>
|
||||
</HBox>
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.columnIndex="0" GridPane.rowIndex="0" GridPane.rowSpan="2147483647">
|
||||
<Label styleClass="text-muted, text-xs" text="BALANCE" />
|
||||
<Label fx:id="amountUs" alignment="CENTER_RIGHT" styleClass="text-lg, channel-balance" text="N/A" />
|
||||
<ProgressBar fx:id="balanceBar" maxWidth="120.0" minHeight="4.0" prefHeight="4.0" progress="0.0" snapToPixel="false">
|
||||
<VBox.margin>
|
||||
<Insets top="3.0" />
|
||||
</VBox.margin>
|
||||
</ProgressBar>
|
||||
</VBox>
|
||||
|
||||
<HBox alignment="BOTTOM_LEFT" spacing="5" GridPane.columnIndex="2" GridPane.columnSpan="3" GridPane.rowIndex="0" GridPane.valignment="BOTTOM">
|
||||
<Label styleClass="text-strong, text-md" text="With" />
|
||||
<Label fx:id="nodeAlias" maxWidth="120.0" styleClass="text-md, channel-peer-alias" visible="false"/>
|
||||
<TextField fx:id="nodeId" editable="false" focusTraversable="false" styleClass="noteditable, text-strong, text-md" text="N/A" HBox.hgrow="ALWAYS" />
|
||||
</HBox>
|
||||
|
||||
<ProgressBar fx:id="balanceBar" minHeight="4.0" prefHeight="4.0" maxWidth="1.7976931348623157E308"
|
||||
progress="0.0" snapToPixel="false"
|
||||
GridPane.columnSpan="4" GridPane.hgrow="ALWAYS" GridPane.rowIndex="1"/>
|
||||
<HBox alignment="CENTER_RIGHT" spacing="5" GridPane.columnIndex="5" GridPane.halignment="RIGHT" GridPane.rowIndex="0" HBox.hgrow="NEVER">
|
||||
<Button fx:id="close" mnemonicParsing="false" styleClass="close-channel" text="Close" visible="false" />
|
||||
<Button fx:id="forceclose" mnemonicParsing="false" styleClass="forceclose-channel" text="Force close" visible="false" />
|
||||
</HBox>
|
||||
|
||||
<Label styleClass="text-muted" text="Funding tx id" GridPane.columnIndex="0" GridPane.rowIndex="2"/>
|
||||
<TextField fx:id="txId" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="2"/>
|
||||
<Label styleClass="text-muted" text="State" GridPane.columnIndex="2" GridPane.rowIndex="1" />
|
||||
<TextField fx:id="state" editable="false" focusTraversable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="3" GridPane.columnSpan="3" GridPane.rowIndex="1" />
|
||||
|
||||
<Label styleClass="text-muted" text="Remote node id" GridPane.columnIndex="0" GridPane.rowIndex="3"/>
|
||||
<TextField fx:id="nodeId" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="3" GridPane.rowIndex="3"/>
|
||||
<Label styleClass="text-muted" text="Funder" GridPane.columnIndex="4" GridPane.halignment="RIGHT" GridPane.rowIndex="4" />
|
||||
<TextField fx:id="funder" editable="false" focusTraversable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="5" GridPane.rowIndex="4" />
|
||||
|
||||
<Label styleClass="text-muted" text="Your balance" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="amountUs" text="N/A" focusTraversable="false" editable="false"
|
||||
styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Channel id" GridPane.columnIndex="2" GridPane.rowIndex="2" />
|
||||
<TextField fx:id="channelId" editable="false" focusTraversable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="3" GridPane.columnSpan="3" GridPane.rowIndex="2" />
|
||||
|
||||
<Label styleClass="text-muted" text="Capacity" GridPane.rowIndex="5"/>
|
||||
<TextField fx:id="capacity" text="N/A" focusTraversable="false" editable="false"
|
||||
styleClass="noteditable"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
<Label styleClass="text-muted" text="Short channel id" GridPane.columnIndex="2" GridPane.rowIndex="3" />
|
||||
<TextField fx:id="shortChannelId" editable="false" focusTraversable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="3" GridPane.columnSpan="3" GridPane.rowIndex="3" />
|
||||
|
||||
<Label styleClass="text-muted" text="Funder" GridPane.columnIndex="2" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="funder" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="3" GridPane.rowIndex="4"/>
|
||||
<Label styleClass="text-muted" text="Funding tx id" GridPane.columnIndex="2" GridPane.rowIndex="4" />
|
||||
<TextField fx:id="txId" editable="false" focusTraversable="false" styleClass="noteditable" text="N/A" GridPane.columnIndex="3" GridPane.rowIndex="4" />
|
||||
|
||||
<Label styleClass="text-muted" text="State" GridPane.columnIndex="2" GridPane.rowIndex="5"/>
|
||||
<TextField fx:id="state" text="N/A" focusTraversable="false" editable="false" styleClass="noteditable"
|
||||
GridPane.columnIndex="3" GridPane.rowIndex="5"/>
|
||||
<HBox prefHeight="100.0" prefWidth="1.0" styleClass="channel-balance-separator" GridPane.columnIndex="1" GridPane.rowSpan="2147483647" />
|
||||
|
||||
</GridPane>
|
||||
<HBox styleClass="channel-separator"/>
|
||||
<HBox styleClass="channel-separator" />
|
||||
</VBox>
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/* ---------- Status Bar ---------- */
|
||||
|
||||
.status-bar {
|
||||
-fx-padding: .5em 1em;
|
||||
-fx-padding: .5em .7em;
|
||||
-fx-background-color: rgb(221,221,221);
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
-fx-border-color: rgb(181,181,181);
|
||||
|
@ -50,18 +50,28 @@
|
|||
}
|
||||
|
||||
.channel .grid {
|
||||
-fx-padding: 10px;
|
||||
-fx-padding: 14px;
|
||||
-fx-vgap: 3px;
|
||||
-fx-hgap: 5px;
|
||||
-fx-hgap: 1em;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.channel-separator {
|
||||
-fx-background-color: rgb(220,220,220);
|
||||
-fx-background-color: rgb(190,200,210);
|
||||
-fx-pref-height: 1px;
|
||||
-fx-padding: 0 -.25em;
|
||||
}
|
||||
|
||||
.channel-balance-separator {
|
||||
-fx-background-color: rgb(225,230,235);
|
||||
}
|
||||
|
||||
.channel-peer-alias {
|
||||
-fx-background-color: rgb(232,233,235);
|
||||
-fx-background-radius: 2;
|
||||
-fx-label-padding: 0 3px;
|
||||
}
|
||||
|
||||
.channels-info {
|
||||
-fx-padding: 4em 0 0 0;
|
||||
}
|
||||
|
|
|
@ -209,51 +209,40 @@
|
|||
</center>
|
||||
<bottom>
|
||||
<HBox fx:id="statusBarBox" styleClass="status-bar" spacing="10">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS" onContextMenuRequested="#openNodeIdContext">
|
||||
<children>
|
||||
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true"
|
||||
preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../commons/images/eclair-shape.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
<Label fx:id="labelNodeId" text="N/A"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="SOMETIMES" minWidth="80.0">
|
||||
<children>
|
||||
<Separator orientation="VERTICAL"/>
|
||||
<Rectangle fx:id="rectRGB" width="7" height="7" fill="transparent"/>
|
||||
<Label fx:id="labelAlias" text="N/A"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="85.0">
|
||||
<children>
|
||||
<Separator orientation="VERTICAL"/>
|
||||
<Label text="HTTP" styleClass="badge, badge-http"/>
|
||||
<Label fx:id="labelApi" styleClass="value" text="N/A"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="85.0">
|
||||
<children>
|
||||
<Separator orientation="VERTICAL"/>
|
||||
<Label text="TCP" styleClass="badge, badge-tcp"/>
|
||||
<Label fx:id="labelServer" text="N/A"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="6.0">
|
||||
<children>
|
||||
<Separator orientation="VERTICAL"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_RIGHT" HBox.hgrow="SOMETIMES" minWidth="195.0">
|
||||
<children>
|
||||
<Label fx:id="bitcoinWallet" text="N/A" textAlignment="RIGHT" textOverrun="CLIP"/>
|
||||
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)" textOverrun="CLIP"/>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS" onContextMenuRequested="#openNodeIdContext">
|
||||
<ImageView fitHeight="16.0" fitWidth="27.0" opacity="0.52" pickOnBounds="true"
|
||||
preserveRatio="true">
|
||||
<Image url="@../commons/images/eclair-shape.png" />
|
||||
</ImageView>
|
||||
<Label fx:id="labelNodeId" text="N/A" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="SOMETIMES" minWidth="160.0">
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label text="TOTAL" styleClass="badge" />
|
||||
<Label fx:id="statusBalanceLabel" styleClass="value" text="N/A" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="SOMETIMES" minWidth="85.0">
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Rectangle fx:id="rectRGB" width="7" height="7" fill="transparent" />
|
||||
<Label fx:id="labelAlias" text="N/A" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="85.0">
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label text="HTTP" styleClass="badge, badge-http" />
|
||||
<Label fx:id="labelApi" styleClass="value" text="N/A" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="80.0">
|
||||
<Separator orientation="VERTICAL" />
|
||||
<Label text="TCP" styleClass="badge, badge-tcp" />
|
||||
<Label fx:id="labelServer" text="N/A" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="6.0">
|
||||
<Separator orientation="VERTICAL" />
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_RIGHT" HBox.hgrow="SOMETIMES" minWidth="155.0">
|
||||
<Label fx:id="bitcoinWallet" text="N/A" textAlignment="RIGHT" textOverrun="CLIP" />
|
||||
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)" textOverrun="CLIP" />
|
||||
</HBox>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<top>
|
||||
|
|
|
@ -89,10 +89,11 @@ class FxApp extends Application with Logging {
|
|||
val unitConf = setup.config.getString("gui.unit")
|
||||
FxApp.unit = Try(CoinUtils.getUnitFromString(unitConf)) match {
|
||||
case Failure(_) =>
|
||||
logger.warn(s"$unitConf is not a valid gui unit, must be msat, sat, mbtc or btc. Defaulting to btc.")
|
||||
logger.warn(s"$unitConf is not a valid gui unit, must be msat, sat, bits, mbtc or btc. Defaulting to btc.")
|
||||
BtcUnit
|
||||
case Success(u) => u
|
||||
}
|
||||
CoinUtils.setCoinPattern(CoinUtils.getPatternFromUnit(FxApp.unit))
|
||||
|
||||
val guiUpdater = system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller), "gui-updater", SupervisorStrategy.Resume))
|
||||
system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
|
||||
|
@ -108,8 +109,8 @@ class FxApp extends Application with Logging {
|
|||
override def run(): Unit = {
|
||||
val scene = new Scene(mainRoot)
|
||||
primaryStage.setTitle("Eclair")
|
||||
primaryStage.setMinWidth(600)
|
||||
primaryStage.setWidth(960)
|
||||
primaryStage.setMinWidth(750)
|
||||
primaryStage.setWidth(980)
|
||||
primaryStage.setMinHeight(400)
|
||||
primaryStage.setHeight(640)
|
||||
primaryStage.setOnCloseRequest(new EventHandler[WindowEvent] {
|
||||
|
|
|
@ -18,12 +18,6 @@ package fr.acinq.eclair.gui
|
|||
|
||||
import java.time.LocalDateTime
|
||||
import java.util.function.Predicate
|
||||
import javafx.application.Platform
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.control.Alert.AlertType
|
||||
import javafx.scene.control.{Alert, ButtonType}
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Terminated}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
|
@ -31,12 +25,18 @@ import fr.acinq.bitcoin._
|
|||
import fr.acinq.eclair.CoinUtils
|
||||
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor.{ZMQConnected, ZMQDisconnected}
|
||||
import fr.acinq.eclair.blockchain.electrum.ElectrumClient.{ElectrumDisconnected, ElectrumReady}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.channel.{Data, _}
|
||||
import fr.acinq.eclair.gui.controllers._
|
||||
import fr.acinq.eclair.payment.PaymentLifecycle.{LocalFailure, PaymentFailed, PaymentSucceeded, RemoteFailure}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router.{NORMAL => _, _}
|
||||
import fr.acinq.eclair.wire.NodeAnnouncement
|
||||
import javafx.application.Platform
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.control.Alert.AlertType
|
||||
import javafx.scene.control.{Alert, ButtonType}
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
|
@ -60,43 +60,23 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
|
|||
|
||||
def receive: Receive = main(Map())
|
||||
|
||||
def createChannelPanel(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, temporaryChannelId: BinaryData): (ChannelPaneController, VBox) = {
|
||||
def createChannelPanel(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, channelId: BinaryData): (ChannelPaneController, VBox) = {
|
||||
log.info(s"new channel: $channel")
|
||||
val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
|
||||
val channelPaneController = new ChannelPaneController(s"$remoteNodeId")
|
||||
val channelPaneController = new ChannelPaneController(channel, remoteNodeId.toString())
|
||||
loader.setController(channelPaneController)
|
||||
val root = loader.load[VBox]
|
||||
channelPaneController.nodeId.setText(remoteNodeId.toString())
|
||||
channelPaneController.channelId.setText(temporaryChannelId.toString())
|
||||
channelPaneController.channelId.setText(channelId.toString())
|
||||
channelPaneController.funder.setText(if (isFunder) "Yes" else "No")
|
||||
channelPaneController.close.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = channel ! CMD_CLOSE(scriptPubKey = None)
|
||||
})
|
||||
channelPaneController.forceclose.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = {
|
||||
val alert = new Alert(AlertType.WARNING, "Careful: force-close is more expensive than a regular close and will incur a delay before funds are spendable.\n\nAre you sure you want to proceed?", ButtonType.YES, ButtonType.NO)
|
||||
alert.showAndWait
|
||||
if (alert.getResult eq ButtonType.YES) {
|
||||
channel ! CMD_FORCECLOSE
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// set the node alias if the node has already been announced
|
||||
mainController.networkNodesList
|
||||
.find(na => na.nodeId.toString.equals(remoteNodeId.toString))
|
||||
.map(na => channelPaneController.updateRemoteNodeAlias(na.alias))
|
||||
.foreach(na => channelPaneController.updateRemoteNodeAlias(na.alias))
|
||||
|
||||
(channelPaneController, root)
|
||||
}
|
||||
|
||||
def updateBalance(channelPaneController: ChannelPaneController, commitments: Commitments) = {
|
||||
val spec = commitments.localCommit.spec
|
||||
channelPaneController.capacity.setText(CoinUtils.formatAmountInUnit(MilliSatoshi(spec.totalFunds), FxApp.getUnit, withUnit = true))
|
||||
channelPaneController.amountUs.setText(CoinUtils.formatAmountInUnit(MilliSatoshi(spec.toLocalMsat), FxApp.getUnit, withUnit = true))
|
||||
channelPaneController.balanceBar.setProgress(spec.toLocalMsat.toDouble / spec.totalFunds)
|
||||
}
|
||||
|
||||
def main(m: Map[ActorRef, ChannelPaneController]): Receive = {
|
||||
|
||||
case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId) =>
|
||||
|
@ -108,29 +88,32 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
|
|||
case ChannelRestored(channel, peer, remoteNodeId, isFunder, channelId, currentData) =>
|
||||
context.watch(channel)
|
||||
val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, channelId)
|
||||
currentData match {
|
||||
case d: HasCommitments =>
|
||||
updateBalance(channelPaneController, d.commitments)
|
||||
channelPaneController.txId.setText(d.commitments.commitInput.outPoint.txid.toString())
|
||||
case _ => {}
|
||||
}
|
||||
runInGuiThread(() => mainController.channelBox.getChildren.addAll(root))
|
||||
context.become(main(m + (channel -> channelPaneController)))
|
||||
channelPaneController.updateBalance(currentData.commitments)
|
||||
val m1 = m + (channel -> channelPaneController)
|
||||
val totalBalance = MilliSatoshi(m1.values.map(_.getBalance.amount).sum)
|
||||
runInGuiThread(() => {
|
||||
channelPaneController.refreshBalance()
|
||||
mainController.refreshTotalBalance(totalBalance)
|
||||
channelPaneController.txId.setText(currentData.commitments.commitInput.outPoint.txid.toString())
|
||||
mainController.channelBox.getChildren.addAll(root)
|
||||
})
|
||||
context.become(main(m1))
|
||||
|
||||
case ShortChannelIdAssigned(channel, channelId, shortChannelId) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
runInGuiThread(() => channelPaneController.shortChannelId.setText(shortChannelId.toString))
|
||||
|
||||
case ChannelIdAssigned(channel, _, _, channelId) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
runInGuiThread(() => channelPaneController.channelId.setText(s"$channelId"))
|
||||
runInGuiThread(() => channelPaneController.channelId.setText(channelId.toString()))
|
||||
|
||||
case ChannelStateChanged(channel, _, _, _, currentState, currentData) if m.contains(channel) =>
|
||||
case ChannelStateChanged(channel, _, remoteNodeId, _, currentState, currentData) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
runInGuiThread { () =>
|
||||
|
||||
(currentState, currentData) match {
|
||||
case (WAIT_FOR_FUNDING_CONFIRMED, d: HasCommitments) =>
|
||||
channelPaneController.txId.setText(d.commitments.commitInput.outPoint.txid.toString())
|
||||
case (WAIT_FOR_FUNDING_CONFIRMED, d: HasCommitments) => channelPaneController.txId.setText(d.commitments.commitInput.outPoint.txid.toString())
|
||||
case _ => {}
|
||||
}
|
||||
|
||||
channelPaneController.close.setVisible(STATE_MUTUAL_CLOSE.contains(currentState))
|
||||
channelPaneController.forceclose.setVisible(STATE_FORCE_CLOSE.contains(currentState))
|
||||
channelPaneController.state.setText(currentState.toString)
|
||||
|
@ -138,19 +121,30 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
|
|||
|
||||
case ChannelSignatureReceived(channel, commitments) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
runInGuiThread(() => updateBalance(channelPaneController, commitments))
|
||||
channelPaneController.updateBalance(commitments)
|
||||
val totalBalance = MilliSatoshi(m.values.map(_.getBalance.amount).sum)
|
||||
runInGuiThread(() => {
|
||||
channelPaneController.refreshBalance()
|
||||
mainController.refreshTotalBalance(totalBalance)
|
||||
})
|
||||
|
||||
case Terminated(actor) if m.contains(actor) =>
|
||||
val channelPaneController = m(actor)
|
||||
log.debug(s"channel=${channelPaneController.channelId.getText} to be removed from gui")
|
||||
runInGuiThread(() => mainController.channelBox.getChildren.remove(channelPaneController.root))
|
||||
val m1 = m - actor
|
||||
val totalBalance = MilliSatoshi(m1.values.map(_.getBalance.amount).sum)
|
||||
runInGuiThread(() => {
|
||||
mainController.refreshTotalBalance(totalBalance)
|
||||
})
|
||||
context.become(main(m1))
|
||||
|
||||
case NodeDiscovered(nodeAnnouncement) =>
|
||||
log.debug(s"peer node discovered with node id=${nodeAnnouncement.nodeId}")
|
||||
runInGuiThread { () =>
|
||||
if (!mainController.networkNodesList.exists(na => na.nodeId == nodeAnnouncement.nodeId)) {
|
||||
mainController.networkNodesList.add(nodeAnnouncement)
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.theirNodeIdValue)) {
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
|
||||
f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
|
||||
})
|
||||
}
|
||||
|
@ -170,7 +164,7 @@ class GUIUpdater(mainController: MainController) extends Actor with ActorLogging
|
|||
val idx = mainController.networkNodesList.indexWhere(na => na.nodeId == nodeAnnouncement.nodeId)
|
||||
if (idx >= 0) {
|
||||
mainController.networkNodesList.update(idx, nodeAnnouncement)
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.theirNodeIdValue)) {
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
|
||||
f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -16,48 +16,59 @@
|
|||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import com.google.common.base.Strings
|
||||
import fr.acinq.bitcoin.MilliSatoshi
|
||||
import fr.acinq.eclair.CoinUtils
|
||||
import fr.acinq.eclair.channel.{CMD_CLOSE, CMD_FORCECLOSE, Commitments}
|
||||
import fr.acinq.eclair.gui.FxApp
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.input.{ContextMenuEvent, MouseEvent}
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.scene.control.Alert.AlertType
|
||||
|
||||
/**
|
||||
* Created by DPA on 23/09/2016.
|
||||
*/
|
||||
class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
|
||||
class ChannelPaneController(val channelRef: ActorRef, val peerNodeId: String) extends Logging {
|
||||
|
||||
@FXML var root: VBox = _
|
||||
@FXML var channelId: TextField = _
|
||||
@FXML var shortChannelId: TextField = _
|
||||
@FXML var txId: TextField = _
|
||||
@FXML var balanceBar: ProgressBar = _
|
||||
@FXML var amountUs: TextField = _
|
||||
@FXML var amountUs: Label = _
|
||||
@FXML var nodeAlias: Label = _
|
||||
@FXML var nodeId: TextField = _
|
||||
@FXML var capacity: TextField = _
|
||||
@FXML var funder: TextField = _
|
||||
@FXML var state: TextField = _
|
||||
@FXML var funder: TextField = _
|
||||
@FXML var close: Button = _
|
||||
@FXML var forceclose: Button = _
|
||||
|
||||
var contextMenu: ContextMenu = _
|
||||
private var contextMenu: ContextMenu = _
|
||||
private var balance: MilliSatoshi = MilliSatoshi(0)
|
||||
private var capacity: MilliSatoshi = MilliSatoshi(0)
|
||||
|
||||
private def buildChannelContextMenu() = {
|
||||
private def buildChannelContextMenu(): Unit = {
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run() = {
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(
|
||||
CopyAction("Copy Channel Id", channelId.getText),
|
||||
CopyAction("Copy Peer Pubkey", theirNodeIdValue),
|
||||
CopyAction("Copy Tx Id", txId.getText())
|
||||
CopyAction("Copy channel id", channelId.getText),
|
||||
CopyAction("Copy peer pubkey", peerNodeId),
|
||||
CopyAction("Copy tx id", txId.getText())
|
||||
))
|
||||
contextMenu.getStyleClass.add("context-channel")
|
||||
channelId.setContextMenu(contextMenu)
|
||||
shortChannelId.setContextMenu(contextMenu)
|
||||
txId.setContextMenu(contextMenu)
|
||||
amountUs.setContextMenu(contextMenu)
|
||||
nodeId.setContextMenu(contextMenu)
|
||||
capacity.setContextMenu(contextMenu)
|
||||
funder.setContextMenu(contextMenu)
|
||||
state.setContextMenu(contextMenu)
|
||||
}
|
||||
|
@ -68,6 +79,38 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
|
|||
channelId.textProperty.addListener(new ChangeListener[String] {
|
||||
override def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String) = buildChannelContextMenu()
|
||||
})
|
||||
nodeId.setText(peerNodeId)
|
||||
nodeAlias.managedProperty.bind(nodeAlias.visibleProperty)
|
||||
close.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = {
|
||||
val alert = new Alert(AlertType.CONFIRMATION,
|
||||
s"""
|
||||
|Are you sure you want to close this channel?
|
||||
|
|
||||
|$getChannelDetails
|
||||
|""".stripMargin, ButtonType.YES, ButtonType.NO)
|
||||
alert.showAndWait
|
||||
if (alert.getResult eq ButtonType.YES) {
|
||||
channelRef ! CMD_CLOSE(scriptPubKey = None)
|
||||
}
|
||||
}
|
||||
})
|
||||
forceclose.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = {
|
||||
val alert = new Alert(AlertType.WARNING,
|
||||
s"""
|
||||
|Careful: force-close is more expensive than a regular close and will incur a delay before funds are spendable.
|
||||
|
|
||||
|Are you sure you want to forcibly close this channel?
|
||||
|
|
||||
|$getChannelDetails
|
||||
""".stripMargin, ButtonType.YES, ButtonType.NO)
|
||||
alert.showAndWait
|
||||
if (alert.getResult eq ButtonType.YES) {
|
||||
channelRef ! CMD_FORCECLOSE
|
||||
}
|
||||
}
|
||||
})
|
||||
buildChannelContextMenu()
|
||||
}
|
||||
|
||||
|
@ -81,6 +124,31 @@ class ChannelPaneController(val theirNodeIdValue: String) extends Logging {
|
|||
}
|
||||
|
||||
def updateRemoteNodeAlias(alias: String) {
|
||||
Option(nodeId).map((n: TextField) => n.setText(s"$theirNodeIdValue ($alias)"))
|
||||
nodeAlias.setText(alias)
|
||||
nodeAlias.setVisible(!Strings.isNullOrEmpty(alias))
|
||||
}
|
||||
|
||||
def updateBalance(commitments: Commitments) {
|
||||
balance = MilliSatoshi(commitments.localCommit.spec.toLocalMsat)
|
||||
capacity = MilliSatoshi(commitments.localCommit.spec.totalFunds)
|
||||
}
|
||||
|
||||
def refreshBalance(): Unit = {
|
||||
amountUs.setText(s"${CoinUtils.formatAmountInUnit(balance, FxApp.getUnit)} / ${CoinUtils.formatAmountInUnit(capacity, FxApp.getUnit, withUnit = true)}")
|
||||
balanceBar.setProgress(balance.amount.toDouble / capacity.amount)
|
||||
}
|
||||
|
||||
def getBalance: MilliSatoshi = balance
|
||||
|
||||
def getCapacity: MilliSatoshi = capacity
|
||||
|
||||
def getChannelDetails: String =
|
||||
s"""
|
||||
|Channel details:
|
||||
|---
|
||||
|Id: ${channelId.getText().substring(0, 18)}...
|
||||
|Peer: ${peerNodeId.toString().substring(0, 18)}...
|
||||
|Balance: ${CoinUtils.formatAmountInUnit(getBalance, FxApp.getUnit, withUnit = true)}
|
||||
|State: ${state.getText}
|
||||
|"""
|
||||
}
|
||||
|
|
|
@ -20,6 +20,17 @@ import java.text.NumberFormat
|
|||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair.NodeParams.{BITCOIND, ELECTRUM}
|
||||
import fr.acinq.eclair.gui.stages._
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.payment.{PaymentEvent, PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import fr.acinq.eclair.{CoinUtils, Setup}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.animation.{FadeTransition, ParallelTransition, SequentialTransition, TranslateTransition}
|
||||
import javafx.application.{HostServices, Platform}
|
||||
import javafx.beans.property._
|
||||
|
@ -36,21 +47,9 @@ import javafx.scene.layout.{AnchorPane, HBox, StackPane, VBox}
|
|||
import javafx.scene.paint.Color
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.scene.text.Text
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import javafx.stage._
|
||||
import javafx.util.{Callback, Duration}
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import fr.acinq.bitcoin.{MilliSatoshi, Satoshi}
|
||||
import fr.acinq.eclair.NodeParams.{BITCOIND, ELECTRUM}
|
||||
import fr.acinq.eclair.gui.stages._
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.payment.{PaymentEvent, PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import fr.acinq.eclair.{CoinUtils, Setup}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
case class ChannelInfo(announcement: ChannelAnnouncement,
|
||||
var feeBaseMsatNode1_opt: Option[Long], var feeBaseMsatNode2_opt: Option[Long],
|
||||
var feeProportionalMillionthsNode1_opt: Option[Long], var feeProportionalMillionthsNode2_opt: Option[Long],
|
||||
|
@ -82,6 +81,7 @@ class MainController(val handlers: Handlers, val hostServices: HostServices) ext
|
|||
|
||||
// status bar elements
|
||||
@FXML var labelNodeId: Label = _
|
||||
@FXML var statusBalanceLabel: Label = _
|
||||
@FXML var rectRGB: Rectangle = _
|
||||
@FXML var labelAlias: Label = _
|
||||
@FXML var labelApi: Label = _
|
||||
|
@ -240,16 +240,15 @@ class MainController(val handlers: Handlers, val hostServices: HostServices) ext
|
|||
networkChannelsFeeBaseMsatNode2Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(
|
||||
pc.getValue.feeBaseMsatNode2_opt.map(f => CoinUtils.formatAmountInUnit(MilliSatoshi(f), FxApp.getUnit, withUnit = true)).getOrElse("?"))
|
||||
// CoinUtils.formatAmountInUnit(MilliSatoshi(pc.getValue.feeBaseMsatNode2), FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
// feeProportionalMillionths is fee per satoshi in millionths of a satoshi
|
||||
networkChannelsFeeProportionalMillionthsNode1Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(
|
||||
pc.getValue.feeProportionalMillionthsNode1_opt.map(f => s"${CoinUtils.COIN_FORMAT.format(f.toDouble / 1000000 * 100)}%").getOrElse("?"))
|
||||
pc.getValue.feeProportionalMillionthsNode1_opt.map(f => s"${NumberFormat.getInstance().format(f.toDouble / 1000000 * 100)}%").getOrElse("?"))
|
||||
})
|
||||
networkChannelsFeeProportionalMillionthsNode2Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(
|
||||
pc.getValue.feeProportionalMillionthsNode2_opt.map(f => s"${CoinUtils.COIN_FORMAT.format(f.toDouble / 1000000 * 100)}%").getOrElse("?"))
|
||||
pc.getValue.feeProportionalMillionthsNode2_opt.map(f => s"${NumberFormat.getInstance().format(f.toDouble / 1000000 * 100)}%").getOrElse("?"))
|
||||
})
|
||||
networkChannelsCapacityColumn.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(
|
||||
|
@ -572,4 +571,8 @@ class MainController(val handlers: Handlers, val hostServices: HostServices) ext
|
|||
childStage.setX(getWindow.map(w => w.getX + w.getWidth / 2 - childStage.getWidth / 2).getOrElse(0))
|
||||
childStage.setY(getWindow.map(w => w.getY + w.getHeight / 2 - childStage.getHeight / 2).getOrElse(0))
|
||||
}
|
||||
|
||||
def refreshTotalBalance(total: MilliSatoshi): Unit = {
|
||||
statusBalanceLabel.setText(CoinUtils.formatAmountInUnit(total, FxApp.getUnit, withUnit = true))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
|
||||
import fr.acinq.eclair.{BtcUnit, MBtcUnit, MSatUnit, SatUnit}
|
||||
import fr.acinq.eclair._
|
||||
|
||||
object Constants {
|
||||
val FX_UNITS_ARRAY_NO_MSAT = FXCollections.observableArrayList(SatUnit.label, MBtcUnit.label, BtcUnit.label)
|
||||
val FX_UNITS_ARRAY = FXCollections.observableArrayList(MSatUnit.label, SatUnit.label, MBtcUnit.label, BtcUnit.label)
|
||||
val FX_UNITS_ARRAY_NO_MSAT = FXCollections.observableArrayList(SatUnit.label, BitUnit.label, MBtcUnit.label, BtcUnit.label)
|
||||
val FX_UNITS_ARRAY = FXCollections.observableArrayList(MSatUnit.label, SatUnit.label, BitUnit.label, MBtcUnit.label, BtcUnit.label)
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue