mirror of
https://github.com/ACINQ/eclair.git
synced 2024-11-20 10:39:19 +01:00
ZMQ connection monitoring (#57)
* made zeromq listener non-blocking and monitors connection status * now throwing an exception at startup in case of zmq connection issues * (gui) added a blocking modal in main window for ZMQ events * made boot error exit the application in headless mode
This commit is contained in:
parent
edefb8d235
commit
e18fcbab47
Binary file not shown.
After Width: | Height: | Size: 3.8 KiB |
@ -171,3 +171,15 @@
|
||||
-fx-text-fill: rgb(166,169,171);
|
||||
-fx-padding: 2px 7px 0 0;
|
||||
}
|
||||
|
||||
/* --------------- Blocker modal ----------------- */
|
||||
.blocker-cover {
|
||||
-fx-background-color: rgba(0,0,0,0.3);
|
||||
}
|
||||
|
||||
.blocker-dialog {
|
||||
-fx-padding: 15px;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #888888;
|
||||
-fx-background-color: #f4f4f4;
|
||||
}
|
@ -6,198 +6,223 @@
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<BorderPane fx:id="root" minHeight="300.0" prefHeight="400.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<center>
|
||||
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab fx:id="channelsTab" closable="false" text="Local Channels">
|
||||
<content>
|
||||
<StackPane>
|
||||
<children>
|
||||
<ScrollPane fitToWidth="true" styleClass="channel-container">
|
||||
<content>
|
||||
<VBox fx:id="channelBox"/>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
<VBox fx:id="channelInfo" alignment="TOP_CENTER" styleClass="channels-info">
|
||||
<AnchorPane fx:id="root" minHeight="300.0" prefHeight="400.0" styleClass="root" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<BorderPane AnchorPane.leftAnchor="0" AnchorPane.rightAnchor="0" AnchorPane.topAnchor="0" AnchorPane.bottomAnchor="0">
|
||||
<center>
|
||||
<TabPane tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab fx:id="channelsTab" closable="false" text="Local Channels">
|
||||
<content>
|
||||
<StackPane>
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="No channels opened yet..."/>
|
||||
<Label styleClass="text-muted"
|
||||
text="You can open a new channel by clicking on "Channels" > "Open Channel...""
|
||||
wrapText="true"/>
|
||||
<ScrollPane fitToWidth="true" styleClass="channel-container">
|
||||
<content>
|
||||
<VBox fx:id="channelBox"/>
|
||||
</content>
|
||||
</ScrollPane>
|
||||
<VBox fx:id="channelInfo" alignment="TOP_CENTER" styleClass="channels-info">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="No channels opened yet..."/>
|
||||
<Label styleClass="text-muted"
|
||||
text="You can open a new channel by clicking on "Channels" > "Open Channel...""
|
||||
wrapText="true"/>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</StackPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Nodes" fx:id="networkNodesTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
<children>
|
||||
<TableView fx:id="networkNodesTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="networkNodesRGBColumn" minWidth="20.0" prefWidth="20.0" maxWidth="20.0" text="" sortable="false"/>
|
||||
<TableColumn fx:id="networkNodesAliasColumn" minWidth="80.0" prefWidth="180.0" maxWidth="300.0" text="Alias"/>
|
||||
<TableColumn fx:id="networkNodesIdColumn" text="Node Id"/>
|
||||
<TableColumn fx:id="networkNodesIPColumn" minWidth="150.0" prefWidth="250.0" maxWidth="300.0" text="IP"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</StackPane>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Nodes" fx:id="networkNodesTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Channels" fx:id="networkChannelsTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
<children>
|
||||
<TableView fx:id="networkChannelsTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="networkChannelsIdColumn" minWidth="120.0" prefWidth="170.0" maxWidth="300.0" text="Channel Id"/>
|
||||
<TableColumn fx:id="networkChannelsNode1Column" text="Node 1"/>
|
||||
<TableColumn fx:id="networkChannelsNode2Column" text="Node 2"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</children>
|
||||
</VBox>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="Activity" closable="false">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
<children>
|
||||
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
|
||||
styleClass="activities-tab" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab fx:id="paymentSentTab" closable="false" text="Sent">
|
||||
<TableView fx:id="paymentSentTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentSentDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentSentAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentFeesColumn" text="Fees Paid (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentReceivedTab" closable="false" text="Received">
|
||||
<TableView fx:id="paymentReceivedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentReceivedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentReceivedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentReceivedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentRelayedTab" closable="false" text="Relayed">
|
||||
<TableView fx:id="paymentRelayedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentRelayedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentRelayedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedFeesColumn" text="Fees Earned (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
<Label AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" textAlignment="RIGHT"
|
||||
maxWidth="180.0" wrapText="true" styleClass="activity-disclaimer"
|
||||
text="Payment history will be erased when the node is shutdown." />
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox fx:id="statusBarBox" styleClass="status-bar" spacing="10">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS" onContextMenuRequested="#openNodeIdContext">
|
||||
<children>
|
||||
<TableView fx:id="networkNodesTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="networkNodesRGBColumn" minWidth="20.0" prefWidth="20.0" maxWidth="20.0" text="" sortable="false"/>
|
||||
<TableColumn fx:id="networkNodesAliasColumn" minWidth="80.0" prefWidth="180.0" maxWidth="300.0" text="Alias"/>
|
||||
<TableColumn fx:id="networkNodesIdColumn" text="Node Id"/>
|
||||
<TableColumn fx:id="networkNodesIPColumn" minWidth="150.0" prefWidth="250.0" maxWidth="300.0" text="IP"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
<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>
|
||||
</VBox>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="All Channels" fx:id="networkChannelsTab" closable="false">
|
||||
<content>
|
||||
<VBox spacing="10.0" styleClass="grid">
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="SOMETIMES" minWidth="80.0">
|
||||
<children>
|
||||
<TableView fx:id="networkChannelsTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="networkChannelsIdColumn" minWidth="120.0" prefWidth="170.0" maxWidth="300.0" text="Channel Id"/>
|
||||
<TableColumn fx:id="networkChannelsNode1Column" text="Node 1"/>
|
||||
<TableColumn fx:id="networkChannelsNode2Column" text="Node 2"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
<Separator orientation="VERTICAL"/>
|
||||
<Rectangle fx:id="rectRGB" width="7" height="7" fill="transparent" />
|
||||
<Label fx:id="labelAlias" text="N/A"/>
|
||||
</children>
|
||||
</VBox>
|
||||
</content>
|
||||
</Tab>
|
||||
<Tab text="Activity" closable="false">
|
||||
<content>
|
||||
<AnchorPane>
|
||||
</HBox>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="NEVER" minWidth="85.0">
|
||||
<children>
|
||||
<TabPane AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" AnchorPane.bottomAnchor="0.0"
|
||||
styleClass="activities-tab" tabClosingPolicy="UNAVAILABLE" BorderPane.alignment="CENTER">
|
||||
<tabs>
|
||||
<Tab fx:id="paymentSentTab" closable="false" text="Sent">
|
||||
<TableView fx:id="paymentSentTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentSentDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentSentAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentFeesColumn" text="Fees Paid (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentReceivedTab" closable="false" text="Received">
|
||||
<TableView fx:id="paymentReceivedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentReceivedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentReceivedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentReceivedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
<Tab fx:id="paymentRelayedTab" closable="false" text="Relayed">
|
||||
<TableView fx:id="paymentRelayedTable" minHeight="50.0" prefHeight="5000.0">
|
||||
<columnResizePolicy><TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/></columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="paymentRelayedDateColumn" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0" text="Date"/>
|
||||
<TableColumn fx:id="paymentRelayedAmountColumn" text="Amount (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedFeesColumn" text="Fees Earned (msat)"
|
||||
styleClass="align-right" resizable="false" minWidth="150.0" prefWidth="150.0" maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedHashColumn" text="Payment Hash"/>
|
||||
</columns>
|
||||
</TableView>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
<Label AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="0.0" textAlignment="RIGHT"
|
||||
maxWidth="180.0" wrapText="true" styleClass="activity-disclaimer"
|
||||
text="Payment history will be erased when the node is shutdown." />
|
||||
<Separator orientation="VERTICAL"/>
|
||||
<Label text="HTTP" styleClass="badge, badge-http"/>
|
||||
<Label fx:id="labelApi" styleClass="value" text="N/A"/>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox styleClass="status-bar" spacing="10">
|
||||
</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 text="Bitcoin-core" textAlignment="RIGHT" textOverrun="CLIP"/>
|
||||
<Label fx:id="bitcoinVersion" text="N/A" textOverrun="CLIP"/>
|
||||
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)" textOverrun="CLIP"/>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</HBox>
|
||||
</bottom>
|
||||
<top>
|
||||
<MenuBar BorderPane.alignment="CENTER">
|
||||
<menus>
|
||||
<Menu mnemonicParsing="false" text="Channels">
|
||||
<items>
|
||||
<MenuItem fx:id="menuOpen" mnemonicParsing="false" onAction="#handleOpenChannel"
|
||||
text="Open channel..."/>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem fx:id="menuSend" mnemonicParsing="false" onAction="#handleSendPayment"
|
||||
text="Send Payment..."/>
|
||||
<MenuItem fx:id="menuReceive" mnemonicParsing="false" onAction="#handleReceivePayment"
|
||||
text="Receive Payment..."/>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleCloseRequest" text="Close"/>
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Tools">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleExportDot" text="Export Graph to .dot"/>
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Help">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleOpenAbout" text="About Eclair..."/>
|
||||
</items>
|
||||
</Menu>
|
||||
</menus>
|
||||
</MenuBar>
|
||||
</top>
|
||||
</BorderPane>
|
||||
<StackPane fx:id="blocker" styleClass="blocker-cover" opacity="0" visible="false" alignment="CENTER"
|
||||
AnchorPane.topAnchor="0" AnchorPane.leftAnchor="0" AnchorPane.bottomAnchor="0" AnchorPane.rightAnchor="0">
|
||||
<children>
|
||||
<HBox alignment="CENTER_LEFT" HBox.hgrow="ALWAYS" onContextMenuRequested="#openNodeIdContext">
|
||||
<HBox fx:id="blockerDialog" opacity="0" styleClass="blocker-dialog" fillHeight="false" alignment="CENTER_LEFT" spacing="20"
|
||||
minWidth="430.0" minHeight="100.0" prefWidth="430.0" prefHeight="100.0" maxWidth="430.0" maxHeight="100.0">
|
||||
<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 fitHeight="40.0" fitWidth="40.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../commons/images/connection_icon.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 text="Bitcoin-core" textAlignment="RIGHT" textOverrun="CLIP"/>
|
||||
<Label fx:id="bitcoinVersion" text="N/A" textOverrun="CLIP"/>
|
||||
<Label fx:id="bitcoinChain" styleClass="chain" text="(N/A)" textOverrun="CLIP"/>
|
||||
<VBox spacing="10.0" GridPane.columnIndex="1">
|
||||
<children>
|
||||
<Label fx:id="message" styleClass="text-strong" text="Lost connection to Bitcoin Core..." wrapText="true" />
|
||||
<Label fx:id="details" text="Make sure that Bitcoin core is up and running." wrapText="true" />
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</HBox>
|
||||
</bottom>
|
||||
</StackPane>
|
||||
</children>
|
||||
<stylesheets>
|
||||
<URL value="@main.css"/>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
</stylesheets>
|
||||
<top>
|
||||
<MenuBar BorderPane.alignment="CENTER">
|
||||
<menus>
|
||||
<Menu mnemonicParsing="false" text="Channels">
|
||||
<items>
|
||||
<MenuItem fx:id="menuOpen" mnemonicParsing="false" onAction="#handleOpenChannel"
|
||||
text="Open channel..."/>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem fx:id="menuSend" mnemonicParsing="false" onAction="#handleSendPayment"
|
||||
text="Send Payment..."/>
|
||||
<MenuItem fx:id="menuReceive" mnemonicParsing="false" onAction="#handleReceivePayment"
|
||||
text="Receive Payment..."/>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleCloseRequest" text="Close"/>
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Tools">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleExportDot" text="Export Graph to .dot"/>
|
||||
</items>
|
||||
</Menu>
|
||||
<Menu mnemonicParsing="false" text="Help">
|
||||
<items>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleOpenAbout" text="About Eclair..."/>
|
||||
</items>
|
||||
</Menu>
|
||||
</menus>
|
||||
</MenuBar>
|
||||
</top>
|
||||
</BorderPane>
|
||||
</AnchorPane>
|
||||
|
@ -2,7 +2,6 @@ package fr.acinq.eclair
|
||||
|
||||
import java.io.File
|
||||
import java.net.InetSocketAddress
|
||||
import javafx.application.Platform
|
||||
|
||||
import akka.actor.{ActorRef, ActorSystem, Props, SupervisorStrategy}
|
||||
import akka.http.scaladsl.Http
|
||||
@ -16,7 +15,7 @@ import com.sun.javafx.application.LauncherImpl
|
||||
import fr.acinq.bitcoin.{Base58Check, OP_CHECKSIG, OP_DUP, OP_EQUALVERIFY, OP_HASH160, OP_PUSHDATA, Script}
|
||||
import fr.acinq.eclair.api.Service
|
||||
import fr.acinq.eclair.blockchain.rpc.BitcoinJsonRPCClient
|
||||
import fr.acinq.eclair.blockchain.zmq.ZeroMQClient
|
||||
import fr.acinq.eclair.blockchain.zmq.ZMQActor
|
||||
import fr.acinq.eclair.blockchain.{ExtendedBitcoinClient, PeerWatcher}
|
||||
import fr.acinq.eclair.channel.Register
|
||||
import fr.acinq.eclair.gui.{FxApp, FxPreloader}
|
||||
@ -30,6 +29,7 @@ import org.slf4j.LoggerFactory
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{Await, ExecutionContext, Promise}
|
||||
import scala.util.Try
|
||||
|
||||
case class CmdLineConfig(datadir: File = new File(System.getProperty("user.home"), ".eclair"), headless: Boolean = false)
|
||||
|
||||
@ -45,11 +45,16 @@ object Boot extends App with Logging {
|
||||
opt[Unit]("headless").optional().action((_, c) => c.copy(headless = true)).text("runs eclair without a gui")
|
||||
}
|
||||
parser.parse(args, CmdLineConfig()) match {
|
||||
case Some(config) if config.headless =>
|
||||
case Some(config) if config.headless => try {
|
||||
val s = new Setup(config.datadir.getAbsolutePath)
|
||||
s.boostrap
|
||||
} catch {
|
||||
case t: Throwable =>
|
||||
logger.error(s"fatal error: ${t.getMessage}")
|
||||
System.exit(1)
|
||||
}
|
||||
case Some(config) => LauncherImpl.launchApplication(classOf[FxApp], classOf[FxPreloader], Array(config.datadir.getAbsolutePath))
|
||||
case None => Platform.exit()
|
||||
case None => System.exit(0)
|
||||
}
|
||||
}
|
||||
|
||||
@ -99,7 +104,8 @@ class Setup(datadir: String, actorSystemName: String = "default") extends Loggin
|
||||
//val finalScriptPubKey = OP_0 :: OP_PUSHDATA(Base58Check.decode(finalAddress)._2) :: Nil
|
||||
val finalScriptPubKey = Script.write(OP_DUP :: OP_HASH160 :: OP_PUSHDATA(Base58Check.decode(finalAddress)._2) :: OP_EQUALVERIFY :: OP_CHECKSIG :: Nil)
|
||||
|
||||
val zmq = new ZeroMQClient(config.getString("bitcoind.zmq"), system.eventStream)
|
||||
val zmqConnected = Promise[Boolean]()
|
||||
val zmq = system.actorOf(SimpleSupervisor.props(Props(new ZMQActor(config.getString("bitcoind.zmq"), Some(zmqConnected))), "zmq", SupervisorStrategy.Restart))
|
||||
val watcher = system.actorOf(SimpleSupervisor.props(PeerWatcher.props(nodeParams, bitcoinClient), "watcher", SupervisorStrategy.Resume))
|
||||
val paymentHandler = system.actorOf(SimpleSupervisor.props(config.getString("payment-handler") match {
|
||||
case "local" => Props[LocalPaymentHandler]
|
||||
@ -110,9 +116,8 @@ class Setup(datadir: String, actorSystemName: String = "default") extends Loggin
|
||||
val router = system.actorOf(SimpleSupervisor.props(Router.props(nodeParams, watcher), "router", SupervisorStrategy.Resume))
|
||||
val switchboard = system.actorOf(SimpleSupervisor.props(Switchboard.props(nodeParams, watcher, router, relayer, finalScriptPubKey), "switchboard", SupervisorStrategy.Resume))
|
||||
val paymentInitiator = system.actorOf(SimpleSupervisor.props(PaymentInitiator.props(nodeParams.privateKey.publicKey, router, register), "payment-initiator", SupervisorStrategy.Restart))
|
||||
val bound = Promise[Unit]()
|
||||
val server = system.actorOf(SimpleSupervisor.props(Server.props(nodeParams, switchboard, new InetSocketAddress(config.getString("server.binding-ip"), config.getInt("server.port")), Some(bound)), "server", SupervisorStrategy.Restart))
|
||||
Await.result(bound.future.recover { case _ => throw new TCPBindException(config.getInt("server.port")) }, 10 seconds)
|
||||
val tcpBound = Promise[Unit]()
|
||||
val server = system.actorOf(SimpleSupervisor.props(Server.props(nodeParams, switchboard, new InetSocketAddress(config.getString("server.binding-ip"), config.getInt("server.port")), Some(tcpBound)), "server", SupervisorStrategy.Restart))
|
||||
|
||||
val _setup = this
|
||||
val api = new Service {
|
||||
@ -123,7 +128,11 @@ class Setup(datadir: String, actorSystemName: String = "default") extends Loggin
|
||||
override val paymentInitiator: ActorRef = _setup.paymentInitiator
|
||||
override val system: ActorSystem = _setup.system
|
||||
}
|
||||
Await.result(Http().bindAndHandle(api.route, config.getString("api.binding-ip"), config.getInt("api.port")).recover { case _ => throw new TCPBindException(config.getInt("api.port")) }, 10 seconds)
|
||||
val httpBound = Http().bindAndHandle(api.route, config.getString("api.binding-ip"), config.getInt("api.port"))
|
||||
|
||||
Try(Await.result(zmqConnected.future, 5 seconds)).recover { case _ => throw ZMQConnectionTimeoutException }.get
|
||||
Try(Await.result(tcpBound.future, 5 seconds)).recover { case _ => throw new TCPBindException(config.getInt("server.port")) }.get
|
||||
Try(Await.result(httpBound, 5 seconds)).recover { case _ => throw new TCPBindException(config.getInt("api.port")) }.get
|
||||
|
||||
val tasks = new Thread(new Runnable() {
|
||||
override def run(): Unit = {
|
||||
@ -156,3 +165,5 @@ object LogSetup {
|
||||
}
|
||||
|
||||
case class TCPBindException(port: Int) extends RuntimeException
|
||||
|
||||
case object ZMQConnectionTimeoutException extends RuntimeException("could not connect to bitcoind using zeromq")
|
||||
|
@ -2,6 +2,8 @@ package fr.acinq.eclair
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, OneForOneStrategy, Props, SupervisorStrategy}
|
||||
|
||||
import scala.concurrent.duration._
|
||||
|
||||
/**
|
||||
* This supervisor will supervise a single child actor using the provided SupervisorStrategy
|
||||
* All incoming messages will be forwarded to the child actor.
|
||||
@ -16,7 +18,8 @@ class SimpleSupervisor(childProps: Props, childName: String, strategy: Superviso
|
||||
case msg => child forward msg
|
||||
}
|
||||
|
||||
override val supervisorStrategy = OneForOneStrategy(loggingEnabled = true) { case _ => strategy }
|
||||
// we allow at most <maxNrOfRetries> within <withinTimeRange>, otherwise the child actor is not restarted (this avoids restart loops)
|
||||
override val supervisorStrategy = OneForOneStrategy(loggingEnabled = true, maxNrOfRetries = 100, withinTimeRange = 1 minute) { case _ => strategy }
|
||||
}
|
||||
|
||||
object SimpleSupervisor {
|
||||
|
@ -0,0 +1,77 @@
|
||||
package fr.acinq.eclair.blockchain.zmq
|
||||
|
||||
import akka.actor.{Actor, ActorLogging}
|
||||
import fr.acinq.bitcoin.{Block, Transaction}
|
||||
import fr.acinq.eclair.blockchain.{NewBlock, NewTransaction}
|
||||
import org.zeromq.ZMQ.Event
|
||||
import org.zeromq.{ZContext, ZMQ, ZMsg}
|
||||
|
||||
import scala.concurrent.Promise
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.Try
|
||||
|
||||
/**
|
||||
* Created by PM on 04/04/2017.
|
||||
*/
|
||||
class ZMQActor(address: String, connected: Option[Promise[Boolean]] = None) extends Actor with ActorLogging {
|
||||
|
||||
val ctx = new ZContext
|
||||
|
||||
val subscriber = ctx.createSocket(ZMQ.SUB)
|
||||
subscriber.monitor("inproc://events", ZMQ.EVENT_CONNECTED | ZMQ.EVENT_DISCONNECTED)
|
||||
subscriber.connect(address)
|
||||
subscriber.subscribe("rawblock".getBytes(ZMQ.CHARSET))
|
||||
subscriber.subscribe("rawtx".getBytes(ZMQ.CHARSET))
|
||||
|
||||
val monitor = ctx.createSocket(ZMQ.PAIR)
|
||||
monitor.connect("inproc://events")
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
// we check messages in a non-blocking manner with an interval, making sure to retrieve all messages before waiting again
|
||||
def checkEvent: Unit = Option(Event.recv(monitor, ZMQ.DONTWAIT)) match {
|
||||
case Some(event) =>
|
||||
self ! event
|
||||
checkEvent
|
||||
case None =>
|
||||
context.system.scheduler.scheduleOnce(1 second)(checkEvent)
|
||||
}
|
||||
|
||||
def checkMsg: Unit = Option(ZMsg.recvMsg(subscriber, ZMQ.DONTWAIT)) match {
|
||||
case Some(msg) =>
|
||||
self ! msg
|
||||
checkMsg
|
||||
case None =>
|
||||
context.system.scheduler.scheduleOnce(1 second)(checkMsg)
|
||||
}
|
||||
|
||||
checkEvent
|
||||
checkMsg
|
||||
|
||||
override def receive: Receive = {
|
||||
|
||||
case event: Event => event.getEvent match {
|
||||
case ZMQ.EVENT_CONNECTED =>
|
||||
log.info(s"connected to ${event.getAddress}")
|
||||
Try(connected.map(_.success(true)))
|
||||
context.system.eventStream.publish(ZMQConnected)
|
||||
case ZMQ.EVENT_DISCONNECTED =>
|
||||
log.warning(s"disconnected from ${event.getAddress}")
|
||||
context.system.eventStream.publish(ZMQDisconnected)
|
||||
case x => log.error(s"unexpected event $x")
|
||||
}
|
||||
|
||||
case msg: ZMsg => msg.popString() match {
|
||||
case "rawblock" =>
|
||||
val block = Block.read(msg.pop().getData)
|
||||
log.debug(s"received blockid=${block.blockId}")
|
||||
context.system.eventStream.publish(NewBlock(block))
|
||||
case "rawtx" =>
|
||||
val tx = Transaction.read(msg.pop().getData)
|
||||
log.debug(s"received txid=${tx.txid}")
|
||||
context.system.eventStream.publish(NewTransaction(tx))
|
||||
case topic => log.warning(s"unexpected topic=$topic")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
package fr.acinq.eclair.blockchain.zmq
|
||||
|
||||
import akka.event.EventStream
|
||||
import fr.acinq.bitcoin.{Block, Transaction}
|
||||
import fr.acinq.eclair.blockchain.{NewBlock, NewTransaction}
|
||||
import grizzled.slf4j.Logging
|
||||
import org.zeromq.ZMsg
|
||||
|
||||
/**
|
||||
* Created by PM on 30/03/2017.
|
||||
*/
|
||||
class ZeroMQClient(addr: String, eventStream: EventStream) extends Logging {
|
||||
|
||||
new Thread(new Runnable {
|
||||
override def run(): Unit = {
|
||||
import org.zeromq.ZMQ
|
||||
val context = ZMQ.context(1)
|
||||
// Connect to weather server
|
||||
val subscriber = context.socket(ZMQ.SUB)
|
||||
subscriber.connect(addr)
|
||||
//subscriber.subscribe("hashblock".getBytes(ZMQ.CHARSET))
|
||||
//subscriber.subscribe("hashtx".getBytes(ZMQ.CHARSET))
|
||||
subscriber.subscribe("rawblock".getBytes(ZMQ.CHARSET))
|
||||
subscriber.subscribe("rawtx".getBytes(ZMQ.CHARSET))
|
||||
while (true) {
|
||||
val msg = ZMsg.recvMsg(subscriber)
|
||||
msg.popString() match {
|
||||
case "rawblock" =>
|
||||
val block = Block.read(msg.pop().getData)
|
||||
logger.debug(s"received blockid=${block.blockId}")
|
||||
eventStream.publish(NewBlock(block))
|
||||
case "rawtx" =>
|
||||
val tx = Transaction.read(msg.pop().getData)
|
||||
logger.debug(s"received txid=${tx.txid}")
|
||||
eventStream.publish(NewTransaction(tx))
|
||||
case _ => {}
|
||||
}
|
||||
}
|
||||
subscriber.close()
|
||||
}
|
||||
}).start()
|
||||
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
package fr.acinq.eclair.blockchain.zmq
|
||||
|
||||
/**
|
||||
* Created by PM on 04/04/2017.
|
||||
*/
|
||||
sealed trait ZMQEvents
|
||||
|
||||
case object ZMQConnected extends ZMQEvents
|
||||
|
||||
case object ZMQDisconnected extends ZMQEvents
|
@ -11,11 +11,12 @@ import javafx.stage.{Popup, Screen, Stage, WindowEvent}
|
||||
|
||||
import akka.actor.{Props, SupervisorStrategy}
|
||||
import akka.stream.StreamTcpException
|
||||
import fr.acinq.eclair.blockchain.zmq.ZMQEvents
|
||||
import fr.acinq.eclair.channel.ChannelEvent
|
||||
import fr.acinq.eclair.gui.controllers.{MainController, NotificationsController}
|
||||
import fr.acinq.eclair.payment.PaymentEvent
|
||||
import fr.acinq.eclair.router.NetworkEvent
|
||||
import fr.acinq.eclair.{Setup, SimpleSupervisor, TCPBindException}
|
||||
import fr.acinq.eclair.{Setup, SimpleSupervisor, TCPBindException, ZMQConnectionTimeoutException}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
|
||||
@ -40,10 +41,11 @@ class FxApp extends Application with Logging {
|
||||
val setup = new Setup(datadir)
|
||||
val handlers = new Handlers(setup)
|
||||
val controller = new MainController(handlers, setup, getHostServices)
|
||||
val guiUpdater = setup.system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller, setup), "gui-updater", SupervisorStrategy.Resume))
|
||||
val guiUpdater = setup.system.actorOf(SimpleSupervisor.props(Props(classOf[GUIUpdater], controller), "gui-updater", SupervisorStrategy.Resume))
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[ChannelEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[PaymentEvent])
|
||||
setup.system.eventStream.subscribe(guiUpdater, classOf[ZMQEvents])
|
||||
|
||||
Platform.runLater(new Runnable {
|
||||
override def run(): Unit = {
|
||||
@ -74,8 +76,11 @@ class FxApp extends Application with Logging {
|
||||
case TCPBindException(port) =>
|
||||
notifyPreloader(new ErrorNotification("Setup", s"Could not bind to port $port", null))
|
||||
case _: ConnectException | _: StreamTcpException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Could not connect to Bitcoin-core.", null))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Please check that Bitcoin-core is started and that the RPC user, password and port are correct."))
|
||||
notifyPreloader(new ErrorNotification("Setup", "Could not connect to Bitcoin Core using JSON-RPC.", null))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Make sure that Bitcoin Core is up and running and RPC parameters are correct."))
|
||||
case ZMQConnectionTimeoutException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Could not connect to Bitcoin Core using ZMQ.", null))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Make sure that Bitcoin Core is up and running and ZMQ parameters are correct."))
|
||||
case t: Throwable =>
|
||||
notifyPreloader(new ErrorNotification("Setup", s"Internal error: ${t.toString}", t))
|
||||
}
|
||||
|
@ -9,14 +9,13 @@ import javafx.scene.layout.VBox
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Terminated}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.Setup
|
||||
import fr.acinq.eclair.blockchain.zmq.{ZMQConnected, ZMQDisconnected}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.gui.controllers.{ChannelPaneController, MainController}
|
||||
import fr.acinq.eclair.io.Reconnect
|
||||
import fr.acinq.eclair.payment.{PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.router.{ChannelDiscovered, ChannelLost, NodeDiscovered, NodeLost}
|
||||
import fr.acinq.eclair.wire.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import org.jgrapht.graph.{DefaultEdge, SimpleGraph}
|
||||
|
||||
import scala.collection.JavaConversions._
|
||||
|
||||
@ -24,14 +23,7 @@ import scala.collection.JavaConversions._
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class GUIUpdater(mainController: MainController, setup: Setup) extends Actor with ActorLogging {
|
||||
|
||||
class NamedEdge(val id: BinaryData) extends DefaultEdge {
|
||||
override def toString: String = s"${id.toString.take(8)}..."
|
||||
}
|
||||
|
||||
val graph = new SimpleGraph[BinaryData, NamedEdge](classOf[NamedEdge])
|
||||
graph.addVertex(setup.nodeParams.privateKey.publicKey)
|
||||
class GUIUpdater(mainController: MainController) extends Actor with ActorLogging {
|
||||
|
||||
def receive: Receive = main(Map())
|
||||
|
||||
@ -153,5 +145,13 @@ class GUIUpdater(mainController: MainController, setup: Setup) extends Actor wit
|
||||
case p: PaymentRelayed =>
|
||||
log.debug(s"payment relayed with h=${p.paymentHash}, amount=${p.amount}, feesEarned=${p.feesEarned}")
|
||||
mainController.paymentRelayedList.prepend(p)
|
||||
|
||||
case ZMQConnected =>
|
||||
log.debug("ZMQ connection online")
|
||||
mainController.hideBlockerModal
|
||||
|
||||
case ZMQDisconnected =>
|
||||
log.debug("ZMQ connection lost")
|
||||
mainController.showBlockerModal
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ class Handlers(setup: Setup) extends Logging {
|
||||
|
||||
import setup._
|
||||
|
||||
private var notifsController:Option[NotificationsController] = None
|
||||
private var notifsController: Option[NotificationsController] = None
|
||||
|
||||
def initNotifications (controller: NotificationsController) = {
|
||||
notifsController = Option(controller)
|
||||
|
@ -4,6 +4,7 @@ import java.text.NumberFormat
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
import java.util.Locale
|
||||
import javafx.animation.{FadeTransition, ParallelTransition, SequentialTransition, TranslateTransition}
|
||||
import javafx.application.{HostServices, Platform}
|
||||
import javafx.beans.property._
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
@ -14,12 +15,12 @@ import javafx.fxml.FXML
|
||||
import javafx.scene.control.TableColumn.CellDataFeatures
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.input.ContextMenuEvent
|
||||
import javafx.scene.layout.{BorderPane, VBox}
|
||||
import javafx.scene.layout.{AnchorPane, HBox, StackPane, VBox}
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.shape.Rectangle
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import javafx.stage._
|
||||
import javafx.util.Callback
|
||||
import javafx.util.{Callback, Duration}
|
||||
|
||||
import fr.acinq.eclair.Setup
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
@ -34,7 +35,7 @@ import grizzled.slf4j.Logging
|
||||
*/
|
||||
class MainController(val handlers: Handlers, val setup: Setup, val hostServices: HostServices) extends Logging {
|
||||
|
||||
@FXML var root: BorderPane = _
|
||||
@FXML var root: AnchorPane = _
|
||||
var contextMenu: ContextMenu = _
|
||||
|
||||
// menu
|
||||
@ -99,6 +100,9 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
||||
@FXML var paymentRelayedHashColumn: TableColumn[PaymentRelayed, String] = _
|
||||
@FXML var paymentRelayedDateColumn: TableColumn[PaymentRelayed, String] = _
|
||||
|
||||
@FXML var blocker: StackPane = _
|
||||
@FXML var blockerDialog: HBox = _
|
||||
|
||||
val PAYMENT_DATE_FORMAT = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
val moneyFormatter = NumberFormat.getInstance(Locale.getDefault)
|
||||
|
||||
@ -358,36 +362,67 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
||||
val fileChooser = new FileChooser
|
||||
fileChooser.setTitle("Save as")
|
||||
fileChooser.getExtensionFilters.addAll(new ExtensionFilter("DOT File (*.dot)", "*.dot"))
|
||||
val file = fileChooser.showSaveDialog(root.getScene.getWindow)
|
||||
val file = fileChooser.showSaveDialog(getWindow.getOrElse(null))
|
||||
if (file != null) handlers.exportToDot(file)
|
||||
}
|
||||
|
||||
@FXML def handleOpenChannel = {
|
||||
val openChannelStage = new OpenChannelStage(handlers, setup)
|
||||
openChannelStage.initOwner(root.getScene.getWindow)
|
||||
openChannelStage.initOwner(getWindow.getOrElse(null))
|
||||
positionAtCenter(openChannelStage)
|
||||
openChannelStage.show
|
||||
}
|
||||
|
||||
@FXML def handleSendPayment = {
|
||||
val sendPaymentStage = new SendPaymentStage(handlers, setup)
|
||||
sendPaymentStage.initOwner(root.getScene.getWindow)
|
||||
sendPaymentStage.initOwner(getWindow.getOrElse(null))
|
||||
positionAtCenter(sendPaymentStage)
|
||||
sendPaymentStage.show
|
||||
}
|
||||
|
||||
@FXML def handleReceivePayment = {
|
||||
val receiveStage = new ReceivePaymentStage(handlers, setup)
|
||||
receiveStage.initOwner(root.getScene.getWindow)
|
||||
receiveStage.initOwner(getWindow.getOrElse(null))
|
||||
positionAtCenter(receiveStage)
|
||||
receiveStage.show
|
||||
}
|
||||
|
||||
@FXML def handleCloseRequest = root.getScene.getWindow.fireEvent(new WindowEvent(root.getScene.getWindow, WindowEvent.WINDOW_CLOSE_REQUEST))
|
||||
def showBlockerModal = {
|
||||
val fadeTransition = new FadeTransition(Duration.millis(300))
|
||||
fadeTransition.setFromValue(0)
|
||||
fadeTransition.setToValue(1)
|
||||
val translateTransition = new TranslateTransition(Duration.millis(300))
|
||||
translateTransition.setFromY(20)
|
||||
translateTransition.setToY(0)
|
||||
blocker.setVisible(true)
|
||||
val ftCover = new FadeTransition(Duration.millis(200), blocker)
|
||||
ftCover.setFromValue(0)
|
||||
ftCover.setToValue(1)
|
||||
ftCover.play
|
||||
val t = new ParallelTransition(blockerDialog, fadeTransition, translateTransition)
|
||||
t.setDelay(Duration.millis(200))
|
||||
t.play
|
||||
}
|
||||
def hideBlockerModal = {
|
||||
val ftCover = new FadeTransition(Duration.millis(400))
|
||||
ftCover.setFromValue(1)
|
||||
ftCover.setToValue(0)
|
||||
val s = new SequentialTransition(blocker, ftCover)
|
||||
s.setOnFinished(new EventHandler[ActionEvent]() {
|
||||
override def handle(event: ActionEvent): Unit = blocker.setVisible(false)
|
||||
})
|
||||
s.play
|
||||
}
|
||||
|
||||
private def getWindow: Option[Window] = {
|
||||
Option(root).map(_.getScene.getWindow)
|
||||
}
|
||||
|
||||
@FXML def handleCloseRequest = getWindow.map(_.fireEvent(new WindowEvent(getWindow.get, WindowEvent.WINDOW_CLOSE_REQUEST)))
|
||||
|
||||
@FXML def handleOpenAbout = {
|
||||
val aboutStage = new AboutStage(hostServices)
|
||||
aboutStage.initOwner(root.getScene.getWindow)
|
||||
aboutStage.initOwner(getWindow.getOrElse(null))
|
||||
positionAtCenter(aboutStage)
|
||||
aboutStage.show
|
||||
}
|
||||
@ -395,7 +430,7 @@ class MainController(val handlers: Handlers, val setup: Setup, val hostServices:
|
||||
@FXML def openNodeIdContext(event: ContextMenuEvent) = contextMenu.show(labelNodeId, event.getScreenX, event.getScreenY)
|
||||
|
||||
def positionAtCenter(childStage: Stage) = {
|
||||
childStage.setX(root.getScene.getWindow.getX + root.getScene.getWindow.getWidth / 2 - childStage.getWidth / 2)
|
||||
childStage.setY(root.getScene.getWindow.getY + root.getScene.getWindow.getHeight / 2 - childStage.getHeight / 2)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user