Remove the GUI (#1981)
So long old friend, and thanks for all the demos!
@ -28,9 +28,7 @@ COPY pom.xml pom.xml
|
||||
COPY eclair-core/pom.xml eclair-core/pom.xml
|
||||
COPY eclair-front/pom.xml eclair-front/pom.xml
|
||||
COPY eclair-node/pom.xml eclair-node/pom.xml
|
||||
COPY eclair-node-gui/pom.xml eclair-node-gui/pom.xml
|
||||
COPY eclair-node/modules/assembly.xml eclair-node/modules/assembly.xml
|
||||
COPY eclair-node-gui/modules/assembly.xml eclair-node-gui/modules/assembly.xml
|
||||
RUN mkdir -p eclair-core/src/main/scala && touch eclair-core/src/main/scala/empty.scala
|
||||
# Blank build. We only care about eclair-node, and we use install because eclair-node depends on eclair-core
|
||||
RUN mvn install -pl eclair-node -am
|
||||
|
@ -97,10 +97,6 @@ eclair-node-<version>-<commit_id>/bin/eclair-node.sh
|
||||
|
||||
You can then control your node via the [eclair-cli](https://github.com/ACINQ/eclair/wiki/Usage) or the [API](https://github.com/ACINQ/eclair/wiki/API).
|
||||
|
||||
This repository also contains a sample GUI ([eclair-node-gui](https://github.com/ACINQ/eclair/tree/master/eclair-node-gui)).
|
||||
|
||||
:rotating_light: The sample GUI should only be used for prototyping and demo purposes: using it on mainnet is unsafe.
|
||||
|
||||
:warning: Be careful when following tutorials/guides that may be outdated or incomplete. You must thoroughly read the official eclair documentation before running your own node.
|
||||
|
||||
## Configuration
|
||||
|
@ -8,4 +8,3 @@ comment:
|
||||
|
||||
ignore:
|
||||
- "eclair-node/**/*"
|
||||
- "eclair-node-gui/**/*"
|
@ -19,12 +19,11 @@ Some actors are long-lived (e.g. lightning channels) while others are very short
|
||||
|
||||
## Top-level projects
|
||||
|
||||
Eclair is split into four top-level projects:
|
||||
Eclair is split into three top-level projects:
|
||||
|
||||
- `eclair-core`: core library implementing lightning
|
||||
- `eclair-node`: server daemon built upon `eclair-core` (exposes a Json RPC and WebSocket endpoint)
|
||||
- `eclair-front`: when using cluster mode, front-end server daemons handling peer connections
|
||||
- `eclair-node-gui` (deprecated): sample JavaFX user interface to demo eclair (should not be used in production)
|
||||
|
||||
The entry point for `eclair-core` is in `Setup.scala`, where we start the actor system, connect to `bitcoind` and create top-level actors.
|
||||
|
||||
|
@ -1,44 +0,0 @@
|
||||
<assembly xmlns="http://maven.apache.org/ASSEMBLY/2.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/ASSEMBLY/2.0.0 http://maven.apache.org/xsd/assembly-2.0.0.xsd">
|
||||
<id>bin</id>
|
||||
<formats>
|
||||
<format>zip</format>
|
||||
</formats>
|
||||
<dependencySets> <!-- include dependencies -->
|
||||
<dependencySet>
|
||||
<outputDirectory>lib</outputDirectory>
|
||||
<useProjectArtifact>true</useProjectArtifact> <!-- include eclair-core and eclair node -->
|
||||
<unpack>false</unpack>
|
||||
<scope>runtime</scope>
|
||||
</dependencySet>
|
||||
</dependencySets>
|
||||
<fileSets> <!-- Include readme and license -->
|
||||
<fileSet>
|
||||
<directory>../</directory>
|
||||
<includes>
|
||||
<include>README.md</include>
|
||||
<include>LICENSE*</include>
|
||||
</includes>
|
||||
</fileSet>
|
||||
<fileSet> <!-- Include the launcher scripts -->
|
||||
<directory>${project.basedir}/src/main/resources</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>eclair-node-gui.sh</include>
|
||||
<include>eclair-node-gui.bat</include>
|
||||
</includes>
|
||||
<fileMode>0755</fileMode>
|
||||
<lineEnding>unix</lineEnding>
|
||||
</fileSet>
|
||||
<fileSet> <!-- Include eclair-cli -->
|
||||
<directory>../eclair-core/</directory>
|
||||
<outputDirectory>bin</outputDirectory>
|
||||
<includes>
|
||||
<include>eclair-cli</include>
|
||||
</includes>
|
||||
<fileMode>0755</fileMode>
|
||||
<lineEnding>unix</lineEnding>
|
||||
</fileSet>
|
||||
</fileSets>
|
||||
</assembly>
|
@ -1,163 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>fr.acinq.eclair</groupId>
|
||||
<artifactId>eclair_2.13</artifactId>
|
||||
<version>0.6.2-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>eclair-node-gui_2.13</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>eclair-node-gui</name>
|
||||
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<configuration>
|
||||
<archive>
|
||||
<manifest>
|
||||
<addClasspath>true</addClasspath>
|
||||
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
|
||||
</manifest>
|
||||
<manifestEntries>
|
||||
<!-- we hide the git commit in the Specification-Version standard field-->
|
||||
<Specification-Version>${git.commit.id}</Specification-Version>
|
||||
<Url>${project.parent.url}</Url>
|
||||
</manifestEntries>
|
||||
</archive>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<artifactId>maven-assembly-plugin</artifactId>
|
||||
<version>3.2.0</version>
|
||||
<configuration>
|
||||
<finalName>${project.name}-${project.version}-${git.commit.id.abbrev}</finalName>
|
||||
<descriptors>
|
||||
<descriptor>modules/assembly.xml</descriptor>
|
||||
</descriptors>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>single</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>installer</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>com.zenjava</groupId>
|
||||
<artifactId>javafx-maven-plugin</artifactId>
|
||||
<version>8.8.3</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<!-- required before build-native -->
|
||||
<id>create-jfxjar</id>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-jar</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>build-native</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<vendor>ACINQ</vendor>
|
||||
<needShortcut>true</needShortcut>
|
||||
<appName>Eclair</appName>
|
||||
<nativeReleaseVersion>${project.version}</nativeReleaseVersion>
|
||||
<skipNativeVersionNumberSanitizing>true</skipNativeVersionNumberSanitizing>
|
||||
<nativeOutputDir>${project.build.directory}/jfx/installer</nativeOutputDir>
|
||||
<mainClass>fr.acinq.eclair.JavafxBoot</mainClass>
|
||||
<preLoader>fr.acinq.eclair.gui.FxPreloader</preLoader>
|
||||
<verbose>false</verbose>
|
||||
<bundler>EXE</bundler>
|
||||
<updateExistingJar>true</updateExistingJar>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>fr.acinq.eclair</groupId>
|
||||
<artifactId>eclair-node_${scala.version.short}</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
<version>3.3.0</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics </artifactId>
|
||||
<version>11.0.2</version>
|
||||
<classifier>win</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics </artifactId>
|
||||
<version>11.0.2</version>
|
||||
<classifier>linux</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-graphics </artifactId>
|
||||
<version>11.0.2</version>
|
||||
<classifier>mac</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-controls</artifactId>
|
||||
<version>11.0.2</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.openjfx</groupId>
|
||||
<artifactId>javafx-fxml</artifactId>
|
||||
<version>11.0.2</version>
|
||||
</dependency>
|
||||
<!-- TESTS -->
|
||||
<dependency>
|
||||
<groupId>com.typesafe.akka</groupId>
|
||||
<artifactId>akka-testkit_${scala.version.short}</artifactId>
|
||||
<version>${akka.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 15 KiB |
@ -1,5 +0,0 @@
|
||||
eclair {
|
||||
gui {
|
||||
unit = btc
|
||||
}
|
||||
}
|
@ -1,192 +0,0 @@
|
||||
@REM Copyright (c) 2012, Joshua Suereth
|
||||
@REM All rights reserved.
|
||||
@REM
|
||||
@REM Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
@REM
|
||||
@REM Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
@REM Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
@REM THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
@REM @@APP_NAME@@ launcher script
|
||||
@REM
|
||||
@REM Environment:
|
||||
@REM JAVA_HOME - location of a JDK home dir (optional if java on path)
|
||||
@REM CFG_OPTS - JVM options (optional)
|
||||
@REM Configuration:
|
||||
@REM @@APP_ENV_NAME@@_config.txt found in the @@APP_ENV_NAME@@_HOME.
|
||||
@setlocal enabledelayedexpansion
|
||||
@setlocal enableextensions
|
||||
|
||||
@echo off
|
||||
|
||||
|
||||
if "%@@APP_ENV_NAME@@_HOME%"=="" (
|
||||
set "APP_HOME=%~dp0\\.."
|
||||
|
||||
rem Also set the old env name for backwards compatibility
|
||||
set "@@APP_ENV_NAME@@_HOME=%~dp0\\.."
|
||||
) else (
|
||||
set "APP_HOME=%@@APP_ENV_NAME@@_HOME%"
|
||||
)
|
||||
|
||||
set "APP_LIB_DIR=%APP_HOME%\lib\"
|
||||
|
||||
rem Detect if we were double clicked, although theoretically A user could
|
||||
rem manually run cmd /c
|
||||
for %%x in (!cmdcmdline!) do if %%~x==/c set DOUBLECLICKED=1
|
||||
|
||||
rem FIRST we load the config file of extra options.
|
||||
set "CFG_FILE=%APP_HOME%\@@APP_ENV_NAME@@_config.txt"
|
||||
set CFG_OPTS=
|
||||
call :parse_config "%CFG_FILE%" CFG_OPTS
|
||||
|
||||
rem We use the value of the JAVA_OPTS environment variable if defined, rather than the config.
|
||||
set _JAVA_OPTS=%JAVA_OPTS%
|
||||
if "!_JAVA_OPTS!"=="" set _JAVA_OPTS=!CFG_OPTS!
|
||||
|
||||
rem We keep in _JAVA_PARAMS all -J-prefixed and -D-prefixed arguments
|
||||
rem "-J" is stripped, "-D" is left as is, and everything is appended to JAVA_OPTS
|
||||
set _JAVA_PARAMS=
|
||||
set _APP_ARGS=
|
||||
|
||||
for /f %%i in ('dir /b %APP_LIB_DIR%\eclair-node-gui*') do set APP_ENTRYPOINT=%%i
|
||||
set CUSTOM_MAIN_CLASS="fr.acinq.eclair.JavafxBoot"
|
||||
set APP_CLASSPATH="%APP_LIB_DIR%;%APP_LIB_DIR%/%APP_ENTRYPOINT%"
|
||||
|
||||
rem Bundled JRE has priority over standard environment variables
|
||||
if defined BUNDLED_JVM (
|
||||
set "_JAVACMD=%BUNDLED_JVM%\bin\java.exe"
|
||||
) else (
|
||||
if "%JAVACMD%" neq "" (
|
||||
set "_JAVACMD=%JAVACMD%"
|
||||
) else (
|
||||
if "%JAVA_HOME%" neq "" (
|
||||
if exist "%JAVA_HOME%\bin\java.exe" set "_JAVACMD=%JAVA_HOME%\bin\java.exe"
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if "%_JAVACMD%"=="" set _JAVACMD=java
|
||||
|
||||
rem Detect if this java is ok to use.
|
||||
for /F %%j in ('"%_JAVACMD%" -version 2^>^&1') do (
|
||||
if %%~j==java set JAVAINSTALLED=1
|
||||
if %%~j==openjdk set JAVAINSTALLED=1
|
||||
)
|
||||
|
||||
rem BAT has no logical or, so we do it OLD SCHOOL! Oppan Redmond Style
|
||||
set JAVAOK=true
|
||||
if not defined JAVAINSTALLED set JAVAOK=false
|
||||
|
||||
if "%JAVAOK%"=="false" (
|
||||
echo.
|
||||
echo A Java JDK is not installed or can't be found.
|
||||
if not "%JAVA_HOME%"=="" (
|
||||
echo JAVA_HOME = "%JAVA_HOME%"
|
||||
)
|
||||
echo.
|
||||
echo Please go to
|
||||
echo https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot
|
||||
echo and download a valid Java JDK and install before running @@APP_NAME@@.
|
||||
echo.
|
||||
echo If you think this message is in error, please check
|
||||
echo your environment variables to see if "java.exe" and "javac.exe" are
|
||||
echo available via JAVA_HOME or PATH.
|
||||
echo.
|
||||
if defined DOUBLECLICKED pause
|
||||
exit /B 1
|
||||
)
|
||||
|
||||
rem if configuration files exist, prepend their contents to the script arguments so it can be processed by this runner
|
||||
call :parse_config "%SCRIPT_CONF_FILE%" SCRIPT_CONF_ARGS
|
||||
|
||||
call :process_args %SCRIPT_CONF_ARGS% %%*
|
||||
|
||||
set _JAVA_OPTS=!_JAVA_OPTS! !_JAVA_PARAMS!
|
||||
|
||||
if defined CUSTOM_MAIN_CLASS (
|
||||
set MAIN_CLASS=!CUSTOM_MAIN_CLASS!
|
||||
) else (
|
||||
set MAIN_CLASS=!APP_MAIN_CLASS!
|
||||
)
|
||||
|
||||
rem Call the application and pass all arguments unchanged.
|
||||
"%_JAVACMD%" !_JAVA_OPTS! !@@APP_ENV_NAME@@_OPTS! -cp %APP_CLASSPATH% %MAIN_CLASS% !_APP_ARGS!
|
||||
|
||||
@endlocal
|
||||
|
||||
exit /B %ERRORLEVEL%
|
||||
|
||||
|
||||
rem Loads a configuration file full of default command line options for this script.
|
||||
rem First argument is the path to the config file.
|
||||
rem Second argument is the name of the environment variable to write to.
|
||||
:parse_config
|
||||
set _PARSE_FILE=%~1
|
||||
set _PARSE_OUT=
|
||||
if exist "%_PARSE_FILE%" (
|
||||
FOR /F "tokens=* eol=# usebackq delims=" %%i IN ("%_PARSE_FILE%") DO (
|
||||
set _PARSE_OUT=!_PARSE_OUT! %%i
|
||||
)
|
||||
)
|
||||
set %2=!_PARSE_OUT!
|
||||
exit /B 0
|
||||
|
||||
|
||||
:add_java
|
||||
set _JAVA_PARAMS=!_JAVA_PARAMS! %*
|
||||
exit /B 0
|
||||
|
||||
|
||||
:add_app
|
||||
set _APP_ARGS=!_APP_ARGS! %*
|
||||
exit /B 0
|
||||
|
||||
|
||||
rem Processes incoming arguments and places them in appropriate global variables
|
||||
:process_args
|
||||
:param_loop
|
||||
call set _PARAM1=%%1
|
||||
set "_TEST_PARAM=%~1"
|
||||
|
||||
if ["!_PARAM1!"]==[""] goto param_afterloop
|
||||
|
||||
|
||||
rem ignore arguments that do not start with '-'
|
||||
if "%_TEST_PARAM:~0,1%"=="-" goto param_java_check
|
||||
set _APP_ARGS=!_APP_ARGS! !_PARAM1!
|
||||
shift
|
||||
goto param_loop
|
||||
|
||||
:param_java_check
|
||||
if "!_TEST_PARAM:~0,2!"=="-J" (
|
||||
rem strip -J prefix
|
||||
set _JAVA_PARAMS=!_JAVA_PARAMS! !_TEST_PARAM:~2!
|
||||
shift
|
||||
goto param_loop
|
||||
)
|
||||
|
||||
if "!_TEST_PARAM:~0,2!"=="-D" (
|
||||
rem test if this was double-quoted property "-Dprop=42"
|
||||
for /F "delims== tokens=1,*" %%G in ("!_TEST_PARAM!") DO (
|
||||
if not ["%%H"] == [""] (
|
||||
set _JAVA_PARAMS=!_JAVA_PARAMS! !_PARAM1!
|
||||
) else if [%2] neq [] (
|
||||
rem it was a normal property: -Dprop=42 or -Drop="42"
|
||||
call set _PARAM1=%%1=%%2
|
||||
set _JAVA_PARAMS=!_JAVA_PARAMS! !_PARAM1!
|
||||
shift
|
||||
)
|
||||
)
|
||||
) else (
|
||||
if "!_TEST_PARAM!"=="-main" (
|
||||
call set CUSTOM_MAIN_CLASS=%%2
|
||||
shift
|
||||
) else (
|
||||
set _APP_ARGS=!_APP_ARGS! !_PARAM1!
|
||||
)
|
||||
)
|
||||
shift
|
||||
goto param_loop
|
||||
:param_afterloop
|
||||
|
||||
exit /B 0
|
@ -1,360 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
# Copyright (c) 2012, Joshua Suereth
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
||||
#
|
||||
# Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
||||
# Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
### ------------------------------- ###
|
||||
### Helper methods for BASH scripts ###
|
||||
### ------------------------------- ###
|
||||
|
||||
die() {
|
||||
echo "$@" 1>&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
realpath () {
|
||||
(
|
||||
TARGET_FILE="$1"
|
||||
CHECK_CYGWIN="$2"
|
||||
|
||||
cd "$(dirname "$TARGET_FILE")"
|
||||
TARGET_FILE=$(basename "$TARGET_FILE")
|
||||
|
||||
COUNT=0
|
||||
while [ -L "$TARGET_FILE" -a $COUNT -lt 100 ]
|
||||
do
|
||||
TARGET_FILE=$(readlink "$TARGET_FILE")
|
||||
cd "$(dirname "$TARGET_FILE")"
|
||||
TARGET_FILE=$(basename "$TARGET_FILE")
|
||||
COUNT=$(($COUNT + 1))
|
||||
done
|
||||
|
||||
if [ "$TARGET_FILE" == "." -o "$TARGET_FILE" == ".." ]; then
|
||||
cd "$TARGET_FILE"
|
||||
TARGET_FILEPATH=
|
||||
else
|
||||
TARGET_FILEPATH=/$TARGET_FILE
|
||||
fi
|
||||
|
||||
# make sure we grab the actual windows path, instead of cygwin's path.
|
||||
if [[ "x$CHECK_CYGWIN" == "x" ]]; then
|
||||
echo "$(pwd -P)/$TARGET_FILE"
|
||||
else
|
||||
echo $(cygwinpath "$(pwd -P)/$TARGET_FILE")
|
||||
fi
|
||||
)
|
||||
}
|
||||
|
||||
# TODO - Do we need to detect msys?
|
||||
|
||||
# Uses uname to detect if we're in the odd cygwin environment.
|
||||
is_cygwin() {
|
||||
local os=$(uname -s)
|
||||
case "$os" in
|
||||
CYGWIN*) return 0 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# This can fix cygwin style /cygdrive paths so we get the
|
||||
# windows style paths.
|
||||
cygwinpath() {
|
||||
local file="$1"
|
||||
if is_cygwin; then
|
||||
echo $(cygpath -w $file)
|
||||
else
|
||||
echo $file
|
||||
fi
|
||||
}
|
||||
|
||||
# Make something URI friendly
|
||||
make_url() {
|
||||
url="$1"
|
||||
local nospaces=${url// /%20}
|
||||
if is_cygwin; then
|
||||
echo "/${nospaces//\\//}"
|
||||
else
|
||||
echo "$nospaces"
|
||||
fi
|
||||
}
|
||||
|
||||
# This crazy function reads in a vanilla "linux" classpath string (only : are separators, and all /),
|
||||
# and returns a classpath with windows style paths, and ; separators.
|
||||
fixCygwinClasspath() {
|
||||
OLDIFS=$IFS
|
||||
IFS=":"
|
||||
read -a classpath_members <<< "$1"
|
||||
declare -a fixed_members
|
||||
IFS=$OLDIFS
|
||||
for i in "${!classpath_members[@]}"
|
||||
do
|
||||
fixed_members[i]=$(realpath "${classpath_members[i]}" "fix")
|
||||
done
|
||||
IFS=";"
|
||||
echo "${fixed_members[*]}"
|
||||
IFS=$OLDIFS
|
||||
}
|
||||
|
||||
# Fix the classpath we use for cygwin.
|
||||
fix_classpath() {
|
||||
cp="$1"
|
||||
if is_cygwin; then
|
||||
echo "$(fixCygwinClasspath "$cp")"
|
||||
else
|
||||
echo "$cp"
|
||||
fi
|
||||
}
|
||||
# Detect if we should use JAVA_HOME or just try PATH.
|
||||
get_java_cmd() {
|
||||
# High-priority override for Jlink images
|
||||
if [[ -n "$bundled_jvm" ]]; then
|
||||
echo "$bundled_jvm/bin/java"
|
||||
elif [[ -n "$JAVA_HOME" ]] && [[ -x "$JAVA_HOME/bin/java" ]]; then
|
||||
echo "$JAVA_HOME/bin/java"
|
||||
else
|
||||
echo "java"
|
||||
fi
|
||||
}
|
||||
|
||||
echoerr () {
|
||||
echo 1>&2 "$@"
|
||||
}
|
||||
vlog () {
|
||||
[[ $verbose || $debug ]] && echoerr "$@"
|
||||
}
|
||||
dlog () {
|
||||
[[ $debug ]] && echoerr "$@"
|
||||
}
|
||||
execRunner () {
|
||||
# print the arguments one to a line, quoting any containing spaces
|
||||
[[ $verbose || $debug ]] && echo "# Executing command line:" && {
|
||||
for arg; do
|
||||
if printf "%s\n" "$arg" | grep -q ' '; then
|
||||
printf "\"%s\"\n" "$arg"
|
||||
else
|
||||
printf "%s\n" "$arg"
|
||||
fi
|
||||
done
|
||||
echo ""
|
||||
}
|
||||
|
||||
# we use "exec" here for our pids to be accurate.
|
||||
exec "$@"
|
||||
}
|
||||
addJava () {
|
||||
dlog "[addJava] arg = '$1'"
|
||||
java_args+=( "$1" )
|
||||
}
|
||||
addApp () {
|
||||
dlog "[addApp] arg = '$1'"
|
||||
app_commands+=( "$1" )
|
||||
}
|
||||
addResidual () {
|
||||
dlog "[residual] arg = '$1'"
|
||||
residual_args+=( "$1" )
|
||||
}
|
||||
addDebugger () {
|
||||
addJava "-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=$1"
|
||||
}
|
||||
|
||||
addKanelaAgent () {
|
||||
addJava "-javaagent:$lib_dir/kanela-agent-1.0.5.jar"
|
||||
}
|
||||
|
||||
require_arg () {
|
||||
local type="$1"
|
||||
local opt="$2"
|
||||
local arg="$3"
|
||||
if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then
|
||||
die "$opt requires <$type> argument"
|
||||
fi
|
||||
}
|
||||
is_function_defined() {
|
||||
declare -f "$1" > /dev/null
|
||||
}
|
||||
|
||||
# Attempt to detect if the script is running via a GUI or not
|
||||
# TODO - Determine where/how we use this generically
|
||||
detect_terminal_for_ui() {
|
||||
[[ ! -t 0 ]] && [[ "${#residual_args}" == "0" ]] && {
|
||||
echo "true"
|
||||
}
|
||||
# SPECIAL TEST FOR MAC
|
||||
[[ "$(uname)" == "Darwin" ]] && [[ "$HOME" == "$PWD" ]] && [[ "${#residual_args}" == "0" ]] && {
|
||||
echo "true"
|
||||
}
|
||||
}
|
||||
|
||||
# Processes incoming arguments and places them in appropriate global variables. called by the run method.
|
||||
process_args () {
|
||||
local no_more_snp_opts=0
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--) shift && no_more_snp_opts=1 && break ;;
|
||||
-h|-help) usage; exit 1 ;;
|
||||
-v|-verbose) verbose=1 && shift ;;
|
||||
-d|-debug) debug=1 && shift ;;
|
||||
-no-version-check) no_version_check=1 && shift ;;
|
||||
-mem) echo "!! WARNING !! -mem option is ignored. Please use -J-Xmx and -J-Xms" && shift 2 ;;
|
||||
-with-kanela) addKanelaAgent && shift ;;
|
||||
-jvm-debug) require_arg port "$1" "$2" && addDebugger $2 && shift 2 ;;
|
||||
-main) custom_mainclass="$2" && shift 2 ;;
|
||||
-java-home) require_arg path "$1" "$2" && jre=`eval echo $2` && java_cmd="$jre/bin/java" && shift 2 ;;
|
||||
-D*|-agentlib*|-XX*) addJava "$1" && shift ;;
|
||||
-J*) addJava "${1:2}" && shift ;;
|
||||
*) addResidual "$1" && shift ;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ no_more_snp_opts ]]; then
|
||||
while [[ $# -gt 0 ]]; do
|
||||
addResidual "$1" && shift
|
||||
done
|
||||
fi
|
||||
|
||||
is_function_defined process_my_args && {
|
||||
myargs=("${residual_args[@]}")
|
||||
residual_args=()
|
||||
process_my_args "${myargs[@]}"
|
||||
}
|
||||
}
|
||||
|
||||
# Actually runs the script.
|
||||
run() {
|
||||
# TODO - check for sane environment
|
||||
|
||||
# process the combined args, then reset "$@" to the residuals
|
||||
process_args "$@"
|
||||
set -- "${residual_args[@]}"
|
||||
argumentCount=$#
|
||||
|
||||
#check for jline terminal fixes on cygwin
|
||||
if is_cygwin; then
|
||||
stty -icanon min 1 -echo > /dev/null 2>&1
|
||||
addJava "-Djline.terminal=jline.UnixTerminal"
|
||||
fi
|
||||
|
||||
# check java version
|
||||
if [[ ! $no_version_check ]]; then
|
||||
java_version_check
|
||||
fi
|
||||
|
||||
if [ -n "$custom_mainclass" ]; then
|
||||
mainclass=("$custom_mainclass")
|
||||
else
|
||||
mainclass=("${app_mainclass[@]}")
|
||||
fi
|
||||
|
||||
# Now we check to see if there are any java opts on the environment. These get listed first, with the script able to override them.
|
||||
if [[ "$JAVA_OPTS" != "" ]]; then
|
||||
java_opts="${JAVA_OPTS}"
|
||||
fi
|
||||
|
||||
execRunner "$java_cmd" \
|
||||
${java_opts[@]} \
|
||||
"${java_args[@]}" \
|
||||
-cp "$(fix_classpath "$app_classpath")" \
|
||||
"${mainclass[@]}" \
|
||||
"${app_commands[@]}" \
|
||||
"${residual_args[@]}"
|
||||
|
||||
local exit_code=$?
|
||||
if is_cygwin; then
|
||||
stty icanon echo > /dev/null 2>&1
|
||||
fi
|
||||
exit $exit_code
|
||||
}
|
||||
|
||||
# Loads a configuration file full of default command line options for this script.
|
||||
loadConfigFile() {
|
||||
cat "$1" | sed $'/^\#/d;s/\r$//'
|
||||
}
|
||||
|
||||
# Now check to see if it's a good enough version
|
||||
java_version_check() {
|
||||
readonly java_version=$("$java_cmd" -version 2>&1 | awk -F '"' '/version/ {print $2}')
|
||||
if [[ "$java_version" == "" ]]; then
|
||||
echo
|
||||
echo No java installations was detected.
|
||||
echo Please go to https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot and download
|
||||
echo
|
||||
exit 1
|
||||
else
|
||||
local major=$(echo "$java_version" | cut -d'.' -f1)
|
||||
if [[ "$major" -eq "1" ]]; then
|
||||
local major=$(echo "$java_version" | cut -d'.' -f2)
|
||||
fi
|
||||
if [[ "$major" -lt "8" ]]; then
|
||||
echo
|
||||
echo The java installation you have is not up to date, eclair-node-gui requires
|
||||
echo at least version 1.8+ \(version 11 recommended\) buy you have version $java_version
|
||||
echo
|
||||
echo Please go to 'https://adoptopenjdk.net/?variant=openjdk11&jvmVariant=hotspot' and download
|
||||
echo a valid Java Runtime and install before running eclair-node-gui.
|
||||
echo
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
### ------------------------------- ###
|
||||
### Start of customized settings ###
|
||||
### ------------------------------- ###
|
||||
usage() {
|
||||
cat <<EOM
|
||||
Usage: $script_name [options]
|
||||
|
||||
-h | -help print this message
|
||||
-v | -verbose this runner is chattier
|
||||
-d | -debug enable debug output for the launcher script
|
||||
-no-version-check Don't run the java version check.
|
||||
-main <classname> Define a custom main class
|
||||
-jvm-debug <port> Turn on JVM debugging, open at the given port.
|
||||
-with-kanela Turn on kanela, akka's monitoring agent
|
||||
|
||||
# java version (default: java from PATH, currently $(java -version 2>&1 | grep version))
|
||||
-java-home <path> alternate JAVA_HOME
|
||||
|
||||
# jvm options and output control
|
||||
JAVA_OPTS environment variable, if unset uses "$java_opts"
|
||||
-Dkey=val pass -Dkey=val directly to the java runtime
|
||||
-J-X pass option -X directly to the java runtime
|
||||
(-J is stripped)
|
||||
|
||||
# special option
|
||||
-- To stop parsing built-in commands from the rest of the command-line.
|
||||
e.g.) enabling debug and sending -d as app argument
|
||||
\$ ./start-script -d -- -d
|
||||
|
||||
In the case of duplicated or conflicting options, basically the order above
|
||||
shows precedence: JAVA_OPTS lowest, command line options highest except "--".
|
||||
EOM
|
||||
}
|
||||
|
||||
### ------------------------------- ###
|
||||
### Main script ###
|
||||
### ------------------------------- ###
|
||||
|
||||
declare -a residual_args
|
||||
declare -a java_args
|
||||
declare -a app_commands
|
||||
declare -r real_script_path="$(realpath "$0")"
|
||||
declare -r app_home="$(realpath "$(dirname "$real_script_path")")"
|
||||
declare -r lib_dir="$(realpath "${app_home:0:${#app_home}-4}/lib")" # {app_home:0:${#app_home}-4} transforms ../bin in ../
|
||||
declare -a app_mainclass=("fr.acinq.eclair.JavafxBoot")
|
||||
declare -a app_entrypoint=$(ls $lib_dir |grep eclair-node-gui) # TODO: improve this
|
||||
declare -a app_classpath=("$lib_dir:$lib_dir/$app_entrypoint")
|
||||
|
||||
# java_cmd is overrode in process_args when -java-home is used
|
||||
declare java_cmd=$(get_java_cmd)
|
||||
|
||||
# if configuration files exist, prepend their contents to $@ so it can be processed by this runner
|
||||
[[ -f "$script_conf_file" ]] && set -- $(loadConfigFile "$script_conf_file") "$@"
|
||||
|
||||
run "$@"
|
@ -1,146 +0,0 @@
|
||||
/* ---------- Root ---------- */
|
||||
|
||||
.root {
|
||||
-fx-font-size: 14px;
|
||||
-fx-text-fill: rgb(80, 82, 84);
|
||||
}
|
||||
|
||||
/* ---------- Text Utilities (color, weight) ---------- */
|
||||
|
||||
.text-mono {
|
||||
-fx-font-family: monospace;
|
||||
}
|
||||
|
||||
.text-strong {
|
||||
-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;
|
||||
}
|
||||
|
||||
.text-error.text-error-downward {
|
||||
-fx-translate-y: 24px;
|
||||
}
|
||||
|
||||
.text-error.text-error-upward {
|
||||
-fx-translate-y: -24px;
|
||||
}
|
||||
|
||||
.label-description {
|
||||
-fx-text-fill: rgb(146,149,151);
|
||||
-fx-font-size: 11px;
|
||||
}
|
||||
|
||||
.link {
|
||||
-fx-text-fill: rgb(25,157,221);
|
||||
-fx-fill: rgb(25,157,221);
|
||||
-fx-underline: true;
|
||||
-fx-cursor: hand;
|
||||
}
|
||||
.link:hover {
|
||||
-fx-text-fill: rgb(93,199,254);
|
||||
-fx-fill: rgb(93,199,254);
|
||||
}
|
||||
|
||||
.text-muted,
|
||||
.label.text-muted {
|
||||
-fx-text-fill: rgb(146,149,151);
|
||||
}
|
||||
|
||||
.align-right {
|
||||
/* useful for table columns */
|
||||
-fx-alignment: CENTER_RIGHT;
|
||||
}
|
||||
|
||||
/* ---------- Context Menu ---------- */
|
||||
|
||||
.context-menu {
|
||||
-fx-padding: 4px;
|
||||
-fx-font-weight: normal;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.context-menu .menu-item:focused {
|
||||
-fx-background-color: rgb(63,179,234);
|
||||
}
|
||||
.context-menu .menu-item:focused .label {
|
||||
-fx-text-fill: white;
|
||||
}
|
||||
.context-menu .separator {
|
||||
-fx-padding: 2px 0;
|
||||
}
|
||||
|
||||
.menu-bar .context-menu {
|
||||
/* font size in menu context popup is standard */
|
||||
-fx-font-size: 14px;
|
||||
}
|
||||
|
||||
/* ---------- Grid Structure ---------- */
|
||||
|
||||
.grid {
|
||||
-fx-vgap: 1em;
|
||||
-fx-hgap: 1em;
|
||||
-fx-padding: 1em;
|
||||
}
|
||||
|
||||
/* ------------- Not Editable TextFields ------------- */
|
||||
/* Java FX text can only be selected if in a TextField or TextArea */
|
||||
/* Make it look like a standard label with the editable = false prop and a special styling */
|
||||
.text-area.noteditable,
|
||||
.text-field.noteditable,
|
||||
.text-field.noteditable:hover,
|
||||
.text-field.noteditable:focused,
|
||||
.noteditable {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-width: 0;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
.text-area.noteditable .scroll-pane {
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
.text-area.noteditable .scroll-pane .viewport{
|
||||
-fx-background-color: transparent;
|
||||
}
|
||||
.text-area.noteditable .scroll-pane .content{
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
/* ---------- Progress Bar ---------- */
|
||||
|
||||
.bar {
|
||||
-fx-background-color: rgb(114,193,229);
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
.track {
|
||||
-fx-background-color: rgb(211,227,234);
|
||||
-fx-background-insets: 0;
|
||||
}
|
||||
|
||||
/* ---------- Forms ----------- */
|
||||
.options-separator {
|
||||
-fx-translate-y: -7px;
|
||||
}
|
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 3.8 KiB |
Before Width: | Height: | Size: 318 B |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 4.4 KiB |
Before Width: | Height: | Size: 3.3 KiB |
Before Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 524 B |
Before Width: | Height: | Size: 526 B |
Before Width: | Height: | Size: 513 B |
Before Width: | Height: | Size: 3.0 KiB |
Before Width: | Height: | Size: 1.5 KiB |
Before Width: | Height: | Size: 6.2 KiB |
@ -1,85 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<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" />
|
||||
</stylesheets>
|
||||
<GridPane prefWidth="600.0" styleClass="grid">
|
||||
<columnConstraints>
|
||||
<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="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>
|
||||
|
||||
<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>
|
||||
|
||||
<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="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="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="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="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="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" />
|
||||
|
||||
<HBox prefHeight="100.0" prefWidth="1.0" styleClass="channel-balance-separator" GridPane.columnIndex="1" GridPane.rowSpan="2147483647" />
|
||||
|
||||
</GridPane>
|
||||
<HBox styleClass="channel-separator" />
|
||||
</VBox>
|
@ -1,246 +0,0 @@
|
||||
/* ---------- Status Bar ---------- */
|
||||
|
||||
.status-bar {
|
||||
-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);
|
||||
}
|
||||
.status-bar .separator:vertical {
|
||||
-fx-padding: -.5em 9px -.5em 0;
|
||||
}
|
||||
|
||||
.status-bar .separator:vertical .line {
|
||||
-fx-background-color: rgb(210,210,210);
|
||||
-fx-border-width: 0 1px 0 0;
|
||||
-fx-border-insets: 0;
|
||||
}
|
||||
|
||||
.status-bar .label {
|
||||
-fx-padding: 2px 0px 2px 5px;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
/* ---------- Bitcoin Chain Color ---------- */
|
||||
|
||||
.status-bar .label.chain {
|
||||
-fx-text-fill: rgb(255,148,40);
|
||||
}
|
||||
.status-bar .label.chain.regtest {
|
||||
-fx-text-fill: rgb(20,208,255);
|
||||
}
|
||||
.status-bar .label.chain.testnet, .status-bar .label.chain.test {
|
||||
-fx-text-fill: rgb(54,207,26);
|
||||
}
|
||||
.status-bar .label.chain.segnet4 {
|
||||
-fx-text-fill: rgb(87,67,246);
|
||||
}
|
||||
|
||||
/* ---------- Protocol badges ---------- */
|
||||
|
||||
.label.badge {
|
||||
-fx-font-size: 11px;
|
||||
-fx-text-fill: rgb(160,160,160);
|
||||
}
|
||||
|
||||
/* ---------- Channels ---------- */
|
||||
|
||||
.channel {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.channel .grid {
|
||||
-fx-padding: 14px;
|
||||
-fx-vgap: 3px;
|
||||
-fx-hgap: 1em;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.channel-separator {
|
||||
-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;
|
||||
}
|
||||
.channel-container {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.context-menu.context-channel .menu-item .label {
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
|
||||
.tab:top:selected {
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
}
|
||||
|
||||
.button.forceclose-channel {
|
||||
-fx-base: #f5dcd8;
|
||||
}
|
||||
|
||||
/* ---------- Table ---------- */
|
||||
|
||||
.table-view {
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0, 0, 0, 0;
|
||||
-fx-border-insets: 0, 0, 0, 0;
|
||||
-fx-padding: 0;
|
||||
-fx-border-color: rgb(200, 200, 200);
|
||||
}
|
||||
|
||||
.table-column:last-visible {
|
||||
-fx-background-insets: 0px, 0px 0px 1px 0, 1px 1px 2px 1px;
|
||||
}
|
||||
|
||||
/* ---------- Notifications ---------- */
|
||||
|
||||
.notifications-box {
|
||||
-fx-background-color: transparent;
|
||||
-fx-padding: 0;
|
||||
}
|
||||
.notification-pane.grid {
|
||||
-fx-background-color: #252525;
|
||||
-fx-border-width: 0 0 0 3px;
|
||||
-fx-padding: 1em;
|
||||
-fx-vgap: 5px;
|
||||
-fx-hgap: 1em;
|
||||
}
|
||||
.notification-pane .label {
|
||||
-fx-text-fill: rgb(255, 255, 255);
|
||||
}
|
||||
.notification-pane .label.notification-title {
|
||||
-fx-text-fill: rgb(220, 220, 220);
|
||||
}
|
||||
.notification-pane .label.notification-message {
|
||||
-fx-font-size: 14px;
|
||||
-fx-font-weight: bold;
|
||||
}
|
||||
.button.notification-close {
|
||||
-fx-background-color: transparent;
|
||||
-fx-background-image: url("../commons/images/close.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: 12px;
|
||||
-fx-background-position: center center;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0;
|
||||
-fx-translate-y: -7px;
|
||||
-fx-translate-x: -3px;
|
||||
}
|
||||
.button.notification-close:hover,
|
||||
.button.notification-close:pressed {
|
||||
-fx-background-color: #353535;
|
||||
}
|
||||
|
||||
.button.notification-copy {
|
||||
-fx-background-color: transparent;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 0;
|
||||
-fx-text-fill: rgb(220, 220, 220);
|
||||
-fx-underline: true;
|
||||
-fx-cursor: hand;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.button.notification-copy:hover,
|
||||
.button.notification-copy:pressed {
|
||||
-fx-text-fill: rgb(240, 240, 240);
|
||||
}
|
||||
|
||||
/* ------------- Activity tab -------------- */
|
||||
|
||||
.activities-tab.tab-pane > *.tab-header-area {
|
||||
-fx-padding: 0;
|
||||
}
|
||||
|
||||
.activities-tab.tab-pane > *.tab-header-area > *.tab-header-background {
|
||||
-fx-background-color: rgb(244,244,244);
|
||||
}
|
||||
|
||||
/* header buttons style */
|
||||
.activities-tab.tab-pane .tab:top {
|
||||
-fx-padding: 0.25em 1em;
|
||||
-fx-background-color: transparent;
|
||||
-fx-focus-color: transparent;
|
||||
-fx-faint-focus-color: transparent;
|
||||
-fx-background-insets: 0;
|
||||
-fx-border-width: 0;
|
||||
}
|
||||
|
||||
/* header buttons style */
|
||||
.activities-tab.tab-pane .tab:top .text {
|
||||
-fx-fill: rgb(100, 104, 108);
|
||||
}
|
||||
.activities-tab.tab-pane .tab:top:selected .text {
|
||||
-fx-font-weight: bold;
|
||||
-fx-fill: rgb(0, 0, 0);
|
||||
}
|
||||
/* table style */
|
||||
.activities-tab .table-view {
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.label.activity-disclaimer {
|
||||
-fx-font-size: 10px;
|
||||
-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;
|
||||
}
|
||||
|
||||
|
||||
/* -------------- Receive Modal ------------------ */
|
||||
|
||||
.result-box {
|
||||
-fx-background-color: #ffffff;
|
||||
-fx-border-width: 1px 0 0 0;
|
||||
-fx-border-color: rgb(210,210,210);
|
||||
}
|
||||
|
||||
.button.copy-clipboard {
|
||||
-fx-background-color: rgb(240,240,240);
|
||||
-fx-background-image: url("../commons/images/copy_icon.png");
|
||||
-fx-background-repeat: no-repeat;
|
||||
-fx-background-size: 12px;
|
||||
-fx-background-position: 4px center;
|
||||
-fx-border-color: transparent;
|
||||
-fx-padding: 2px 4px 2px 20px;
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.button.copy-clipboard:hover {
|
||||
-fx-background-color: rgb(230,232,235);
|
||||
}
|
||||
.button.copy-clipboard:pressed {
|
||||
-fx-background-color: rgb(220,222,225);
|
||||
}
|
||||
|
||||
/* --------- send modal ---------- */
|
||||
|
||||
.text-field.description-text, .text-area.description-text {
|
||||
-fx-background-color: rgb(235,235,235);
|
||||
-fx-text-fill: rgb(100,100,100);
|
||||
-fx-padding: 4px;
|
||||
}
|
@ -1,336 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.input.KeyCodeCombination?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.shape.Rectangle?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.net.URL?>
|
||||
<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>
|
||||
<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>
|
||||
</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="200.0"
|
||||
text="Short Channel Id"/>
|
||||
<TableColumn fx:id="networkChannelsCapacityColumn" text="Capacity"
|
||||
minWidth="80.0" prefWidth="120.0" maxWidth="200.0"/>
|
||||
<TableColumn fx:id="networkChannelsNode1Column" text="Node 1"/>
|
||||
<TableColumn fx:id="networkChannelsFeeBaseMsatNode1Column"
|
||||
minWidth="100.0" prefWidth="120.0" maxWidth="200.0"
|
||||
text="Node 1 Base Fee"/>
|
||||
<TableColumn fx:id="networkChannelsFeeProportionalMillionthsNode1Column"
|
||||
minWidth="60.0" prefWidth="120.0" maxWidth="200.0"
|
||||
text="Node 1 Proportional Fee"/>
|
||||
<TableColumn fx:id="networkChannelsDirectionsColumn"
|
||||
minWidth="30.0" prefWidth="30.0" maxWidth="30.0"/>
|
||||
<TableColumn fx:id="networkChannelsNode2Column" text="Node 2"/>
|
||||
<TableColumn fx:id="networkChannelsFeeBaseMsatNode2Column"
|
||||
minWidth="100.0" prefWidth="120.0" maxWidth="200.0"
|
||||
text="Node 2 Base Fee"/>
|
||||
<TableColumn fx:id="networkChannelsFeeProportionalMillionthsNode2Column"
|
||||
minWidth="60.0" prefWidth="120.0" maxWidth="200.0"
|
||||
text="Node 2 Proportional Fee"/>
|
||||
</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"
|
||||
styleClass="align-right" resizable="false"
|
||||
minWidth="150.0" prefWidth="150.0"
|
||||
maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentFeesColumn"
|
||||
text="Fees Paid"
|
||||
styleClass="align-right" resizable="false"
|
||||
minWidth="150.0" prefWidth="150.0"
|
||||
maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentSentHashColumn"
|
||||
text="Payment Hash"/>
|
||||
<TableColumn fx:id="paymentSentPreimageColumn"
|
||||
text="Payment Preimage"/>
|
||||
</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"
|
||||
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"
|
||||
styleClass="align-right" resizable="false"
|
||||
minWidth="150.0" prefWidth="150.0"
|
||||
maxWidth="150.0"/>
|
||||
<TableColumn fx:id="paymentRelayedFeesColumn"
|
||||
text="Fees Earned"
|
||||
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 cleared when the node is shut down."/>
|
||||
</children>
|
||||
</AnchorPane>
|
||||
</content>
|
||||
</Tab>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
</center>
|
||||
<bottom>
|
||||
<HBox fx:id="statusBarBox" styleClass="status-bar" spacing="10">
|
||||
<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>
|
||||
<MenuBar BorderPane.alignment="CENTER">
|
||||
<menus>
|
||||
<Menu mnemonicParsing="false" text="Channels">
|
||||
<items>
|
||||
<MenuItem fx:id="menuOpen" mnemonicParsing="false" onAction="#handleOpenChannel"
|
||||
text="Open channel...">
|
||||
<accelerator>
|
||||
<KeyCodeCombination code="O" control="DOWN" alt="UP" meta="UP" shift="UP"
|
||||
shortcut="UP"/>
|
||||
</accelerator>
|
||||
</MenuItem>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem fx:id="menuSend" mnemonicParsing="false" onAction="#handleSendPayment"
|
||||
text="Send Payment...">
|
||||
<accelerator>
|
||||
<KeyCodeCombination code="P" control="DOWN" alt="UP" meta="UP" shift="UP"
|
||||
shortcut="UP"/>
|
||||
</accelerator>
|
||||
</MenuItem>
|
||||
<MenuItem fx:id="menuReceive" mnemonicParsing="false" onAction="#handleReceivePayment"
|
||||
text="Receive Payment...">
|
||||
<accelerator>
|
||||
<KeyCodeCombination code="N" control="DOWN" alt="UP" meta="UP" shift="UP"
|
||||
shortcut="UP"/>
|
||||
</accelerator>
|
||||
</MenuItem>
|
||||
<SeparatorMenuItem mnemonicParsing="false"/>
|
||||
<MenuItem mnemonicParsing="false" onAction="#handleCloseRequest" text="Close">
|
||||
<accelerator>
|
||||
<KeyCodeCombination code="Q" control="DOWN" alt="UP" meta="UP" shift="UP"
|
||||
shortcut="UP"/>
|
||||
</accelerator>
|
||||
</MenuItem>
|
||||
</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 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="40.0" fitWidth="40.0" pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../commons/images/connection_icon.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
<VBox spacing="10.0" GridPane.columnIndex="1">
|
||||
<children>
|
||||
<TextFlow>
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong"
|
||||
text="Lost connection to "/>
|
||||
<Text fx:id="blockerDialogTitleEngineName" strokeType="OUTSIDE"
|
||||
strokeWidth="0.0" styleClass="text-strong"/>
|
||||
<Text strokeType="OUTSIDE" styleClass="text-strong" strokeWidth="0.0"
|
||||
text="..."/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
<TextFlow>
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-sm"
|
||||
text="Please check your connection."/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</HBox>
|
||||
</children>
|
||||
</StackPane>
|
||||
</children>
|
||||
<stylesheets>
|
||||
<URL value="@main.css"/>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
</stylesheets>
|
||||
</AnchorPane>
|
@ -1,52 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane fx:id="rootPane" maxWidth="400.0" minWidth="400.0" onMouseEntered="#handleMouseEnter" onMouseExited="#handleMouseExit" prefWidth="400.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="25.0" prefWidth="25.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="320.0" />
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="30.0" minWidth="5.0" prefWidth="20.0" />
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="SOMETIMES" />
|
||||
<RowConstraints minHeight="10.0" valignment="TOP" vgrow="ALWAYS" />
|
||||
<RowConstraints minHeight="8.0" valignment="TOP" vgrow="ALWAYS" />
|
||||
</rowConstraints>
|
||||
<styleClass>
|
||||
<String fx:value="grid" />
|
||||
<String fx:value="notification-pane" />
|
||||
</styleClass>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css" />
|
||||
<URL value="@main.css" />
|
||||
</stylesheets>
|
||||
<Label fx:id="titleLabel" styleClass="notification-title" text="Eclair Notification" GridPane.columnIndex="1" />
|
||||
<ImageView fx:id="icon" fitWidth="25.0" pickOnBounds="true" preserveRatio="true" GridPane.rowSpan="2">
|
||||
<Image url="@../commons/images/eclair-square.png" />
|
||||
</ImageView>
|
||||
<Button fx:id="closeButton" maxHeight="18.0" maxWidth="18.0" minHeight="18.0" minWidth="18.0" mnemonicParsing="false" styleClass="notification-close" GridPane.columnIndex="2" />
|
||||
<Label fx:id="messageLabel" styleClass="notification-message" wrapText="true" GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1" maxHeight="280.0" />
|
||||
<Button fx:id="copyButton" mnemonicParsing="false" styleClass="notification-copy" text="Copy the whole message" GridPane.rowIndex="2" GridPane.columnIndex="1" />
|
||||
</GridPane>
|
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
<?import java.net.URL?>
|
||||
<VBox fx:id="notifsVBox" spacing="10.0"
|
||||
style="-fx-background-color: transparent" styleClass="notifications-box"
|
||||
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@main.css"/>
|
||||
</stylesheets>
|
||||
</VBox>
|
@ -1,82 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import javafx.scene.text.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane prefWidth="500.0" prefHeight="200.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" maxWidth="120.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0"/>
|
||||
</columnConstraints>
|
||||
<children>
|
||||
<ImageView fitHeight="120.0" fitWidth="120.0" pickOnBounds="true" preserveRatio="true"
|
||||
GridPane.halignment="CENTER">
|
||||
<image>
|
||||
<Image url="@../commons/images/eclair-square.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
<VBox spacing="10.0" styleClass="about-content" GridPane.columnIndex="1">
|
||||
<children>
|
||||
<TextFlow>
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong" text="Eclair v"/>
|
||||
<Text fx:id="version" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="text-strong"
|
||||
text="Unknown"/>
|
||||
<Text strokeType="OUTSIDE" styleClass="text-sm" strokeWidth="0.0" text=" brought to you by "/>
|
||||
<Text onMouseClicked="#openACINQPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
|
||||
text="ACINQ"/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
<TextFlow layoutX="10.0" layoutY="90.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Eclair follows "/>
|
||||
<Text onMouseClicked="#openLNRFCPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
|
||||
text="the Lightning Network specifications"/>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
<TextFlow layoutX="10.0" layoutY="10.0">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="The source code is available from "/>
|
||||
<Text onMouseClicked="#openGithubPage" strokeType="OUTSIDE" strokeWidth="0.0" styleClass="link"
|
||||
text="GitHub"/>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
<TextFlow layoutX="10.0" layoutY="90.0" styleClass="">
|
||||
<children>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="Licensed under "/>
|
||||
<Text onMouseClicked="#openApacheLicencePage" strokeType="OUTSIDE" strokeWidth="0.0"
|
||||
styleClass="link" text="the Apache 2 License"/>
|
||||
<Text strokeType="OUTSIDE" strokeWidth="0.0" text="."/>
|
||||
</children>
|
||||
</TextFlow>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="grid"/>
|
||||
</styleClass>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
</stylesheets>
|
||||
</GridPane>
|
@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TextArea?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane fx:id="resultBox" styleClass="grid, result-box" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1" prefWidth="590.0">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" maxWidth="250.0" minWidth="10.0" prefWidth="250.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="200.0" prefWidth="240.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@../main/main.css"/>
|
||||
</stylesheets>
|
||||
<ImageView fx:id="uriQRCode" fitWidth="250.0" pickOnBounds="true" preserveRatio="true"
|
||||
GridPane.rowIndex="0" GridPane.columnIndex="0"/>
|
||||
<VBox spacing="10.0" GridPane.rowIndex="0" GridPane.columnIndex="1">
|
||||
<HBox spacing="10.0" alignment="CENTER_LEFT">
|
||||
<Label text="URI:" styleClass="text-strong"/>
|
||||
<Button mnemonicParsing="false" onAction="#handleCopyURI"
|
||||
styleClass="copy-clipboard"
|
||||
text="Copy to Clipboard" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
</HBox>
|
||||
<TextArea fx:id="uriTextarea" prefHeight="200.0" editable="false"
|
||||
styleClass="noteditable, text-sm, text-mono" wrapText="true"/>
|
||||
</VBox>
|
||||
</GridPane>
|
@ -1,97 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane styleClass="grid" prefWidth="550.0" prefHeight="400.0" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints hgrow="SOMETIMES" maxWidth="180.0" minWidth="10.0" prefWidth="180.0" halignment="RIGHT"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="180.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="160.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES" maxHeight="10.0"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES" minHeight="30.0" valignment="BOTTOM"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
<RowConstraints vgrow="SOMETIMES"/>
|
||||
</rowConstraints>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
</stylesheets>
|
||||
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="0">
|
||||
<Label styleClass="text-strong" text="Target Node URI"/>
|
||||
<Label styleClass="label-description" text="Address of the node" textAlignment="RIGHT" wrapText="true"/>
|
||||
</VBox>
|
||||
<TextField fx:id="host" prefWidth="313.0" promptText="pubkey@host:port"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="0"/>
|
||||
<Label fx:id="hostError" styleClass="text-error, text-error-downward" mouseTransparent="true"
|
||||
GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
|
||||
|
||||
<CheckBox fx:id="simpleConnection" mnemonicParsing="false" text="Simple connection (no channel)"
|
||||
styleClass="text-sm"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="1"/>
|
||||
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="2">
|
||||
<Label styleClass="text-strong" text="Capacity"/>
|
||||
<Label styleClass="label-description" text="Funding capacity of the channel" textAlignment="RIGHT"
|
||||
wrapText="true"/>
|
||||
</VBox>
|
||||
<TextField fx:id="fundingSat" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
<ComboBox fx:id="fundingUnit" prefWidth="150.0" GridPane.columnIndex="2" GridPane.rowIndex="2"/>
|
||||
<Label fx:id="fundingSatError" styleClass="text-error, text-error-downward"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="2"/>
|
||||
|
||||
<Label styleClass="text-muted" text="Optional Parameters" wrapText="true" GridPane.columnIndex="0"
|
||||
GridPane.rowIndex="3"/>
|
||||
<Separator styleClass="options-separator" GridPane.columnIndex="1" GridPane.rowIndex="3"
|
||||
GridPane.columnSpan="2"/>
|
||||
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="4">
|
||||
<Label styleClass="text-strong" text="Fee rate"/>
|
||||
<Label styleClass="label-description" text="Funding tx fee rate, in sat/byte" textAlignment="RIGHT"
|
||||
wrapText="true"/>
|
||||
</VBox>
|
||||
<TextField fx:id="feerateField" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
<Label fx:id="feerateError" styleClass="text-error, text-error-downward"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="4"/>
|
||||
|
||||
<VBox alignment="CENTER_RIGHT" GridPane.rowIndex="5">
|
||||
<Label styleClass="text-strong" text="Push Amount (msat)"/>
|
||||
<Label styleClass="label-description" text="Sent when opening channel" textAlignment="RIGHT"
|
||||
wrapText="true"/>
|
||||
</VBox>
|
||||
<TextField fx:id="pushMsatField" prefWidth="313.0" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
<Label fx:id="pushMsatError" styleClass="text-error, text-error-downward"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="5" GridPane.columnSpan="2"/>
|
||||
|
||||
<CheckBox fx:id="publicChannel" mnemonicParsing="true" selected="true" styleClass="text-sm"
|
||||
text="Public Channel"
|
||||
GridPane.columnIndex="1" GridPane.columnSpan="2" GridPane.rowIndex="6"/>
|
||||
|
||||
<Button defaultButton="true" mnemonicParsing="false" onAction="#handleOpen" text="Connect"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="7" GridPane.valignment="BOTTOM"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
|
||||
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="7"
|
||||
GridPane.valignment="BOTTOM"/>
|
||||
</GridPane>
|
@ -1,110 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<VBox xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" prefWidth="590.0">
|
||||
<children>
|
||||
<GridPane styleClass="grid">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="RIGHT" hgrow="SOMETIMES" maxWidth="250.0" minWidth="10.0"
|
||||
prefWidth="250.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="120.0"/>
|
||||
<ColumnConstraints hgrow="SOMETIMES" minWidth="10.0" prefWidth="120.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<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>
|
||||
<children>
|
||||
<VBox alignment="TOP_RIGHT" GridPane.rowIndex="0">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Optional amount to receive"/>
|
||||
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT"
|
||||
text="Maximum of ~0.042 BTC"/>
|
||||
</children>
|
||||
</VBox>
|
||||
<TextField fx:id="amount" GridPane.columnIndex="1" GridPane.rowIndex="0"/>
|
||||
<ComboBox fx:id="unit" GridPane.columnIndex="2" GridPane.rowIndex="0" GridPane.halignment="RIGHT" />
|
||||
<Label fx:id="amountError" opacity="0.0" styleClass="text-error, text-error-downward"
|
||||
text="Generic Invalid Amount"
|
||||
mouseTransparent="true" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
|
||||
|
||||
<VBox alignment="TOP_RIGHT" GridPane.rowIndex="1" GridPane.columnIndex="0">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Optional description"/>
|
||||
<Label styleClass="label-description" wrapText="true" textAlignment="RIGHT"
|
||||
text="Can be left empty"/>
|
||||
</children>
|
||||
</VBox>
|
||||
<TextArea fx:id="description" GridPane.columnIndex="1" GridPane.rowIndex="1" GridPane.columnSpan="2"
|
||||
wrapText="true" prefHeight="50.0"/>
|
||||
|
||||
<VBox alignment="TOP_RIGHT" GridPane.rowIndex="2" GridPane.columnIndex="0">
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Add the lightning: prefix"/>
|
||||
</children>
|
||||
</VBox>
|
||||
<CheckBox fx:id="prependPrefixCheckbox" selected="false" GridPane.rowIndex="2" GridPane.columnIndex="1"/>
|
||||
|
||||
<Button defaultButton="true" mnemonicParsing="false" onAction="#handleGenerate" prefHeight="29.0"
|
||||
prefWidth="95.0" text="Generate" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel"
|
||||
text="Close"
|
||||
GridPane.columnIndex="2" GridPane.halignment="RIGHT" GridPane.rowIndex="3" opacity="0"
|
||||
focusTraversable="false"/>
|
||||
|
||||
</children>
|
||||
</GridPane>
|
||||
<GridPane fx:id="resultBox" styleClass="grid, result-box" visible="false">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" maxWidth="250.0" minWidth="10.0" prefWidth="250.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS" minWidth="200.0" prefWidth="240.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<ImageView fx:id="paymentRequestQRCode" fitWidth="250.0" pickOnBounds="true" preserveRatio="true"
|
||||
GridPane.rowIndex="0" GridPane.columnIndex="0"></ImageView>
|
||||
<VBox spacing="10.0" GridPane.rowIndex="0" GridPane.columnIndex="1">
|
||||
<children>
|
||||
<HBox spacing="10.0" alignment="CENTER_LEFT">
|
||||
<children>
|
||||
<Label text="Invoice:" styleClass="text-strong"/>
|
||||
<Button mnemonicParsing="false" onAction="#handleCopyInvoice"
|
||||
styleClass="copy-clipboard"
|
||||
text="Copy to Clipboard" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||
</children>
|
||||
</HBox>
|
||||
<TextArea fx:id="paymentRequestTextArea" prefHeight="200.0" editable="false"
|
||||
styleClass="noteditable, text-sm, text-mono" wrapText="true"/>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</GridPane>
|
||||
</children>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@../main/main.css"/>
|
||||
</stylesheets>
|
||||
</VBox>
|
@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.*?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.lang.String?>
|
||||
<?import java.net.URL?>
|
||||
<GridPane fx:id="nodeId" prefWidth="550.0" prefHeight="550.0" xmlns="http://javafx.com/javafx/8"
|
||||
xmlns:fx="http://javafx.com/fxml/1">
|
||||
<columnConstraints>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="SOMETIMES" minWidth="10.0" prefWidth="110.0"/>
|
||||
<ColumnConstraints halignment="LEFT" hgrow="ALWAYS" minWidth="10.0" prefWidth="250.0"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="1.0" prefHeight="3.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="1.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
<RowConstraints minHeight="10.0" vgrow="SOMETIMES"/>
|
||||
</rowConstraints>
|
||||
<children>
|
||||
<Label styleClass="text-strong" text="Enter a Payment Request below" GridPane.columnSpan="2"
|
||||
GridPane.valignment="TOP"/>
|
||||
<TextArea fx:id="paymentRequest" minHeight="150.0" prefHeight="150.0" styleClass="ta" wrapText="true"
|
||||
GridPane.columnSpan="2" GridPane.rowIndex="1" GridPane.vgrow="ALWAYS"/>
|
||||
|
||||
<Label fx:id="paymentRequestError" mouseTransparent="true" styleClass="text-error" GridPane.columnSpan="2"
|
||||
GridPane.rowIndex="2"/>
|
||||
|
||||
<Label text="Node Id" GridPane.halignment="RIGHT" GridPane.rowIndex="3"/>
|
||||
<TextField fx:id="nodeIdField" focusTraversable="false" editable="false" styleClass="description-text"
|
||||
text="N/A"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||
|
||||
<Label text="Payment Hash" GridPane.halignment="RIGHT" GridPane.rowIndex="4"/>
|
||||
<TextField fx:id="paymentHashField" focusTraversable="false" editable="false" styleClass="description-text"
|
||||
text="N/A"
|
||||
GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||
|
||||
<Label fx:id="descriptionLabel" text="Description" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE"
|
||||
GridPane.rowIndex="5"/>
|
||||
<TextArea fx:id="descriptionField" focusTraversable="false" editable="false"
|
||||
styleClass="noteditable, description-text" text="N/A"
|
||||
prefHeight="80.0" maxHeight="80.0" GridPane.columnIndex="1" GridPane.rowIndex="5"/>
|
||||
|
||||
<Label fx:id="amountFieldLabel" text="Amount" GridPane.halignment="RIGHT" GridPane.valignment="BASELINE" GridPane.rowIndex="6"/>
|
||||
<VBox GridPane.columnIndex="1" GridPane.rowIndex="6">
|
||||
<children>
|
||||
<TextField fx:id="amountField"/>
|
||||
<Label fx:id="amountFieldError" mouseTransparent="true" styleClass="text-error"/>
|
||||
</children>
|
||||
</VBox>
|
||||
|
||||
<Separator GridPane.columnSpan="2" GridPane.rowIndex="7"/>
|
||||
|
||||
<Button fx:id="sendButton" defaultButton="true" mnemonicParsing="false" onAction="#handleSend" text="Send"
|
||||
GridPane.rowIndex="8"/>
|
||||
<Button cancelButton="true" mnemonicParsing="false" onAction="#handleClose" styleClass="cancel" text="Cancel"
|
||||
GridPane.columnIndex="1" GridPane.halignment="RIGHT" GridPane.rowIndex="8"/>
|
||||
</children>
|
||||
<styleClass>
|
||||
<String fx:value="grid"/>
|
||||
<String fx:value="modal"/>
|
||||
</styleClass>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@../main/main.css"/>
|
||||
</stylesheets>
|
||||
</GridPane>
|
@ -1,18 +0,0 @@
|
||||
.label.splash-error-label {
|
||||
-fx-padding: .25em;
|
||||
}
|
||||
|
||||
.error-box {
|
||||
-fx-padding: 1em;
|
||||
-fx-border-width: 1px;
|
||||
-fx-border-color: #dddddd;
|
||||
-fx-background-color: #f6f6f6;
|
||||
}
|
||||
|
||||
.error-box .label {
|
||||
-fx-font-size: 12px;
|
||||
}
|
||||
.error-box .button {
|
||||
-fx-font-size: 12px;
|
||||
-fx-faint-focus-color: transparent;
|
||||
}
|
@ -1,71 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.effect.BoxBlur?>
|
||||
<?import javafx.scene.effect.DropShadow?>
|
||||
<?import javafx.scene.image.Image?>
|
||||
<?import javafx.scene.image.ImageView?>
|
||||
<?import javafx.scene.layout.*?>
|
||||
<?import java.net.URL?>
|
||||
<Pane fx:id="splash" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity"
|
||||
prefHeight="457.0" prefWidth="760.0" style="-fx-background-color: transparent"
|
||||
xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<ImageView fx:id="imgBlurred" fitHeight="0" fitWidth="300.0" layoutX="176.0" layoutY="115.0" pickOnBounds="true"
|
||||
preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../commons/images/eclair-fit.png"/>
|
||||
</image>
|
||||
<effect>
|
||||
<BoxBlur height="114.75" width="92.44"/>
|
||||
</effect>
|
||||
</ImageView>
|
||||
<ImageView fx:id="img" fitHeight="0" fitWidth="409.0" layoutX="176.0" layoutY="114.0" opacity="0.0"
|
||||
pickOnBounds="true" preserveRatio="true">
|
||||
<image>
|
||||
<Image url="@../commons/images/eclair-fit.png"/>
|
||||
</image>
|
||||
</ImageView>
|
||||
<VBox fx:id="errorBox" opacity="0.0" alignment="CENTER" layoutX="196.0" prefWidth="370.0" prefHeight="457.0">
|
||||
<children>
|
||||
<VBox prefWidth="370.0" styleClass="error-box" spacing="10">
|
||||
<effect>
|
||||
<DropShadow offsetX="5.0" offsetY="5.0" radius="25.0" color="rgba(0,0,0,.4)"
|
||||
blurType="GAUSSIAN"/>
|
||||
</effect>
|
||||
<children>
|
||||
<VBox fx:id="logBox" VBox.vgrow="ALWAYS" styleClass="log-box">
|
||||
<children>
|
||||
</children>
|
||||
</VBox>
|
||||
<Label onMouseClicked="#openGithubPage" VBox.vgrow="NEVER" styleClass="link"
|
||||
text="Check our readme to get started."/>
|
||||
<Button fx:id="closeButton" VBox.vgrow="NEVER" mnemonicParsing="false" onAction="#closeAndKill"
|
||||
text="Close" cancelButton="true"/>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
</VBox>
|
||||
</children>
|
||||
<stylesheets>
|
||||
<URL value="@../commons/globals.css"/>
|
||||
<URL value="@splash.css"/>
|
||||
</stylesheets>
|
||||
</Pane>
|
@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair
|
||||
|
||||
import java.io.File
|
||||
|
||||
import akka.actor.ActorSystem
|
||||
import fr.acinq.eclair.gui.{FxApp, FxPreloader}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.application.Application
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
/**
|
||||
* Created by PM on 25/01/2016.
|
||||
*/
|
||||
object JavafxBoot extends App with Logging {
|
||||
try {
|
||||
val datadir = new File(System.getProperty("eclair.datadir", System.getProperty("user.home") + "/.eclair"))
|
||||
val config = NodeParams.loadConfiguration(datadir)
|
||||
val headless = System.getProperty("eclair.headless") != null
|
||||
|
||||
if (headless) {
|
||||
implicit val system = ActorSystem("eclair-node-gui", config)
|
||||
val setup = new Setup(datadir, pluginParams = Seq.empty)
|
||||
setup.bootstrap.map { kit =>
|
||||
Boot.startApiServiceIfEnabled(kit)
|
||||
}
|
||||
} else {
|
||||
System.setProperty("javafx.preloader", classOf[FxPreloader].getName)
|
||||
Application.launch(classOf[FxApp], datadir.getAbsolutePath)
|
||||
}
|
||||
} catch {
|
||||
case t: Throwable =>
|
||||
val errorMsg = if (t.getMessage != null) t.getMessage else t.getClass.getSimpleName
|
||||
System.err.println(s"fatal error: $errorMsg")
|
||||
logger.error(s"fatal error: $errorMsg", t)
|
||||
System.exit(1)
|
||||
}
|
||||
}
|
@ -1,172 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui
|
||||
|
||||
import java.io.File
|
||||
|
||||
import akka.actor.{ActorSystem, Props, SupervisorStrategy}
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor._
|
||||
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 grizzled.slf4j.Logging
|
||||
import javafx.application.Preloader.ErrorNotification
|
||||
import javafx.application.{Application, Platform}
|
||||
import javafx.event.EventHandler
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Popup, Screen, Stage, WindowEvent}
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.Promise
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class FxApp extends Application with Logging {
|
||||
|
||||
override def init = {
|
||||
logger.debug("initializing application...")
|
||||
}
|
||||
|
||||
def onError(t: Throwable): Unit = t match {
|
||||
case e@TCPBindException(port) =>
|
||||
notifyPreloader(new ErrorNotification("Setup", s"Could not bind to port $port", e))
|
||||
case e: BitcoinRPCConnectionException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Could not connect to Bitcoin Core using JSON-RPC.", e))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Make sure that Bitcoin Core is up and running and RPC parameters are correct."))
|
||||
case e@BitcoinZMQConnectionTimeoutException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Could not connect to Bitcoin Core using ZMQ.", e))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Make sure that Bitcoin Core is up and running and ZMQ parameters are correct."))
|
||||
case e@IncompatibleDBException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Breaking changes!", e))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Eclair is still in alpha, and under heavy development. Last update was not backward compatible."))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Please reset your datadir."))
|
||||
case e@IncompatibleNetworkDBException =>
|
||||
notifyPreloader(new ErrorNotification("Setup", "Unreadable network database!", e))
|
||||
notifyPreloader(new AppNotification(InfoAppNotification, "Could not read the network database. Please remove the file and restart."))
|
||||
case t: Throwable =>
|
||||
notifyPreloader(new ErrorNotification("Setup", s"Error: ${t.getLocalizedMessage}", t))
|
||||
}
|
||||
|
||||
override def start(primaryStage: Stage): Unit = {
|
||||
new Thread(new Runnable {
|
||||
override def run(): Unit = {
|
||||
try {
|
||||
val icon = new Image(getClass.getResource("/gui/commons/images/eclair-square.png").toExternalForm, false)
|
||||
primaryStage.getIcons.add(icon)
|
||||
val mainFXML = new FXMLLoader(getClass.getResource("/gui/main/main.fxml"))
|
||||
val pKit = Promise[Kit]()
|
||||
val handlers = new Handlers(pKit.future)
|
||||
val controller = new MainController(handlers, getHostServices)
|
||||
mainFXML.setController(controller)
|
||||
val mainRoot = mainFXML.load[Parent]
|
||||
val datadir = new File(getParameters.getUnnamed.get(0))
|
||||
val config = NodeParams.loadConfiguration(datadir)
|
||||
implicit val system = ActorSystem("eclair-node-gui", config)
|
||||
val setup = new Setup(datadir, pluginParams = Seq.empty)
|
||||
|
||||
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, 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])
|
||||
system.eventStream.subscribe(guiUpdater, classOf[NetworkEvent])
|
||||
system.eventStream.subscribe(guiUpdater, classOf[PaymentEvent])
|
||||
system.eventStream.subscribe(guiUpdater, classOf[ZMQEvent])
|
||||
pKit.completeWith(setup.bootstrap)
|
||||
pKit.future.onComplete {
|
||||
case Success(kit) =>
|
||||
Boot.startApiServiceIfEnabled(kit)
|
||||
Platform.runLater(new Runnable {
|
||||
override def run(): Unit = {
|
||||
val scene = new Scene(mainRoot)
|
||||
primaryStage.setTitle("Eclair")
|
||||
primaryStage.setMinWidth(750)
|
||||
primaryStage.setWidth(980)
|
||||
primaryStage.setMinHeight(400)
|
||||
primaryStage.setHeight(640)
|
||||
primaryStage.setOnCloseRequest(new EventHandler[WindowEvent] {
|
||||
override def handle(event: WindowEvent): Unit = {
|
||||
logger.info(s"Close Requested")
|
||||
System.exit(0)
|
||||
}
|
||||
})
|
||||
controller.initInfoFields(setup)
|
||||
primaryStage.setScene(scene)
|
||||
primaryStage.show
|
||||
notifyPreloader(new AppNotification(SuccessAppNotification, "Init successful"))
|
||||
initNotificationStage(primaryStage, handlers)
|
||||
}
|
||||
})
|
||||
case Failure(t) => onError(t)
|
||||
}
|
||||
} catch {
|
||||
case t: Throwable => onError(t)
|
||||
}
|
||||
}
|
||||
}).start
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the notification stage and assign it to the handler class.
|
||||
*
|
||||
* @param owner stage owning the notification stage
|
||||
* @param notifhandlers Handles the notifications
|
||||
*/
|
||||
private def initNotificationStage(owner: Stage, notifhandlers: Handlers) = {
|
||||
// get fxml/controller
|
||||
val notifFXML = new FXMLLoader(getClass.getResource("/gui/main/notifications.fxml"))
|
||||
val notifsController = new NotificationsController
|
||||
notifFXML.setController(notifsController)
|
||||
val root = notifFXML.load[Parent]
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = {
|
||||
// create scene
|
||||
val popup = new Popup
|
||||
popup.setHideOnEscape(false)
|
||||
popup.setAutoFix(false)
|
||||
val margin = 10
|
||||
val width = 400
|
||||
popup.setWidth(margin + width)
|
||||
popup.getContent.add(root)
|
||||
// positioning the popup @ TOP RIGHT of screen
|
||||
val screenBounds = Screen.getPrimary.getVisualBounds
|
||||
popup.show(owner, screenBounds.getMaxX - (margin + width), screenBounds.getMinY + margin)
|
||||
notifhandlers.initNotifications(notifsController)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
object FxApp {
|
||||
private var unit: CoinUnit = BtcUnit
|
||||
def getUnit = FxApp.unit
|
||||
}
|
@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui
|
||||
|
||||
import fr.acinq.eclair.gui.controllers.SplashController
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.application.Preloader
|
||||
import javafx.application.Preloader.{ErrorNotification, PreloaderNotification}
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.paint.Color
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Stage, StageStyle}
|
||||
|
||||
sealed trait AppNotificationType
|
||||
|
||||
case object SuccessAppNotification extends AppNotificationType
|
||||
|
||||
case object InfoAppNotification extends AppNotificationType
|
||||
|
||||
case class AppNotification(notificationType: AppNotificationType, message: String) extends PreloaderNotification
|
||||
|
||||
/**
|
||||
* Created by DPA on 15/03/2017.
|
||||
*/
|
||||
class FxPreloader extends Preloader with Logging {
|
||||
|
||||
var controller: Option[SplashController] = None
|
||||
var stage: Option[Stage] = None
|
||||
|
||||
override def start(primaryStage: Stage) = {
|
||||
setupStage(primaryStage)
|
||||
primaryStage.show
|
||||
stage = Option(primaryStage)
|
||||
}
|
||||
|
||||
private def setupStage(stage: Stage) = {
|
||||
val icon = new Image(getClass.getResource("/gui/commons/images/eclair-square.png").toExternalForm, false)
|
||||
stage.getIcons.add(icon)
|
||||
|
||||
// set stage props
|
||||
stage.initStyle(StageStyle.TRANSPARENT)
|
||||
stage.setResizable(false)
|
||||
|
||||
// get fxml/controller
|
||||
val splashController = new SplashController(getHostServices)
|
||||
val splash = new FXMLLoader(getClass.getResource("/gui/splash/splash.fxml"))
|
||||
splash.setController(splashController)
|
||||
val root = splash.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
scene.setFill(Color.TRANSPARENT)
|
||||
|
||||
stage.setScene(scene)
|
||||
controller = Option(splashController)
|
||||
}
|
||||
|
||||
override def handleApplicationNotification(info: PreloaderNotification) = {
|
||||
info match {
|
||||
case n: ErrorNotification =>
|
||||
logger.debug(s"Preloader error notification => ${n.getDetails}")
|
||||
logger.error("", n.getCause)
|
||||
controller.map(_.addError(n.getDetails))
|
||||
controller.map(_.showErrorBox)
|
||||
case n: AppNotification =>
|
||||
logger.debug(s"Preloader app notification => ${n.notificationType}, ${n.message}")
|
||||
n.notificationType match {
|
||||
case SuccessAppNotification => stage.map(_.close)
|
||||
case InfoAppNotification => controller.map(_.addLog(n.message))
|
||||
case _ =>
|
||||
}
|
||||
case _ =>
|
||||
logger.debug(s"Notification ${info}")
|
||||
}
|
||||
}
|
||||
}
|
@ -1,233 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui
|
||||
|
||||
import java.time.LocalDateTime
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorRef, Terminated}
|
||||
import fr.acinq.bitcoin.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin._
|
||||
import fr.acinq.eclair.CoinUtils
|
||||
import fr.acinq.eclair.blockchain.bitcoind.zmq.ZMQActor.{ZMQConnected, ZMQDisconnected}
|
||||
import fr.acinq.eclair.channel._
|
||||
import fr.acinq.eclair.gui.controllers._
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.router.Router.{NORMAL => _}
|
||||
import fr.acinq.eclair.router._
|
||||
import javafx.application.Platform
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
|
||||
class GUIUpdater(mainController: MainController) extends Actor with ActorLogging {
|
||||
|
||||
val STATE_MUTUAL_CLOSE: Set[ChannelState] = Set(WAIT_FOR_INIT_INTERNAL, WAIT_FOR_OPEN_CHANNEL, WAIT_FOR_ACCEPT_CHANNEL, WAIT_FOR_FUNDING_INTERNAL, WAIT_FOR_FUNDING_CREATED, WAIT_FOR_FUNDING_SIGNED, NORMAL)
|
||||
val STATE_FORCE_CLOSE: Set[ChannelState] = Set(WAIT_FOR_FUNDING_CONFIRMED, WAIT_FOR_FUNDING_LOCKED, NORMAL, SHUTDOWN, NEGOTIATING, OFFLINE, SYNCING)
|
||||
|
||||
/**
|
||||
* Needed to stop JavaFX complaining about updates from non GUI thread
|
||||
*/
|
||||
private def runInGuiThread(f: () => Unit): Unit = {
|
||||
Platform.runLater(new Runnable() {
|
||||
@Override def run(): Unit = f()
|
||||
})
|
||||
}
|
||||
|
||||
def receive: Receive = main(Map())
|
||||
|
||||
def createChannelPanel(channel: ActorRef, peer: ActorRef, remoteNodeId: PublicKey, isFunder: Boolean, channelId: ByteVector32): (ChannelPaneController, VBox) = {
|
||||
log.info(s"new channel: $channel")
|
||||
val loader = new FXMLLoader(getClass.getResource("/gui/main/channelPane.fxml"))
|
||||
val channelPaneController = new ChannelPaneController(channel, remoteNodeId.toString())
|
||||
loader.setController(channelPaneController)
|
||||
val root = loader.load[VBox]
|
||||
channelPaneController.channelId.setText(channelId.toHex)
|
||||
channelPaneController.funder.setText(if (isFunder) "Yes" else "No")
|
||||
|
||||
// set the node alias if the node has already been announced
|
||||
if (mainController.networkNodesMap.containsKey(remoteNodeId)) {
|
||||
val na = mainController.networkNodesMap.get(remoteNodeId)
|
||||
channelPaneController.updateRemoteNodeAlias(na.alias)
|
||||
}
|
||||
|
||||
(channelPaneController, root)
|
||||
}
|
||||
|
||||
def main(m: Map[ActorRef, ChannelPaneController]): Receive = {
|
||||
|
||||
case ChannelCreated(channel, peer, remoteNodeId, isFunder, temporaryChannelId, _, _) =>
|
||||
context.watch(channel)
|
||||
val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, isFunder, temporaryChannelId)
|
||||
runInGuiThread(() => mainController.channelBox.getChildren.addAll(root))
|
||||
context.become(main(m + (channel -> channelPaneController)))
|
||||
|
||||
case ChannelRestored(channel, channelId, peer, remoteNodeId, currentData) => // We are specifically interested in normal Commitments with funding txid here
|
||||
context.watch(channel)
|
||||
val (channelPaneController, root) = createChannelPanel(channel, peer, remoteNodeId, currentData.commitments.localParams.isFunder, channelId)
|
||||
channelPaneController.updateBalance(currentData.commitments)
|
||||
val m1 = m + (channel -> channelPaneController)
|
||||
val totalBalance = m1.values.map(_.getBalance).sum
|
||||
runInGuiThread(() => {
|
||||
channelPaneController.refreshBalance()
|
||||
mainController.refreshTotalBalance(totalBalance)
|
||||
channelPaneController.txId.setText(currentData.commitments.commitInput.outPoint.txid.toHex)
|
||||
mainController.channelBox.getChildren.addAll(root)
|
||||
})
|
||||
context.become(main(m1))
|
||||
|
||||
case ShortChannelIdAssigned(channel, _, 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(channelId.toHex))
|
||||
|
||||
case ChannelStateChanged(channel, _, _, _, _, currentState, commitments_opt) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
runInGuiThread { () =>
|
||||
(currentState, commitments_opt) match {
|
||||
case (WAIT_FOR_FUNDING_CONFIRMED, Some(c: Commitments)) => channelPaneController.txId.setText(c.commitInput.outPoint.txid.toHex)
|
||||
case _ =>
|
||||
}
|
||||
channelPaneController.close.setVisible(STATE_MUTUAL_CLOSE.contains(currentState))
|
||||
channelPaneController.forceclose.setVisible(STATE_FORCE_CLOSE.contains(currentState))
|
||||
channelPaneController.state.setText(currentState.toString)
|
||||
}
|
||||
|
||||
case ChannelSignatureReceived(channel, commitments) if m.contains(channel) =>
|
||||
val channelPaneController = m(channel)
|
||||
channelPaneController.updateBalance(commitments)
|
||||
val totalBalance = m.values.map(_.getBalance).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 = m1.values.map(_.getBalance).sum
|
||||
runInGuiThread(() => {
|
||||
mainController.refreshTotalBalance(totalBalance)
|
||||
})
|
||||
context.become(main(m1))
|
||||
|
||||
case NodesDiscovered(nodeAnnouncements) =>
|
||||
runInGuiThread { () =>
|
||||
nodeAnnouncements.foreach { nodeAnnouncement =>
|
||||
log.debug(s"peer node discovered with node id={}", nodeAnnouncement.nodeId)
|
||||
if (!mainController.networkNodesMap.containsKey(nodeAnnouncement.nodeId)) {
|
||||
mainController.networkNodesMap.put(nodeAnnouncement.nodeId, nodeAnnouncement)
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
|
||||
f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case NodeLost(nodeId) =>
|
||||
log.debug(s"peer node lost with node id=$nodeId")
|
||||
runInGuiThread { () =>
|
||||
mainController.networkNodesMap.remove(nodeId)
|
||||
}
|
||||
|
||||
case NodeUpdated(nodeAnnouncement) =>
|
||||
log.debug(s"peer node with id=${nodeAnnouncement.nodeId} has been updated")
|
||||
runInGuiThread { () =>
|
||||
mainController.networkNodesMap.put(nodeAnnouncement.nodeId, nodeAnnouncement)
|
||||
m.foreach(f => if (nodeAnnouncement.nodeId.toString.equals(f._2.peerNodeId)) {
|
||||
f._2.updateRemoteNodeAlias(nodeAnnouncement.alias)
|
||||
})
|
||||
}
|
||||
|
||||
case ChannelsDiscovered(channelsDiscovered) =>
|
||||
runInGuiThread { () =>
|
||||
channelsDiscovered.foreach { case SingleChannelDiscovered(channelAnnouncement, capacity, _, _) =>
|
||||
log.debug(s"peer channel discovered with channel id={}", channelAnnouncement.shortChannelId)
|
||||
if (!mainController.networkChannelsMap.containsKey(channelAnnouncement.shortChannelId)) {
|
||||
mainController.networkChannelsMap.put(channelAnnouncement.shortChannelId, ChannelInfo(channelAnnouncement, None, None, None, None, capacity, None, None))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case ChannelLost(shortChannelId) =>
|
||||
log.debug(s"peer channel lost with channel id=$shortChannelId")
|
||||
runInGuiThread { () =>
|
||||
mainController.networkChannelsMap.remove(shortChannelId)
|
||||
}
|
||||
|
||||
case ChannelUpdatesReceived(channelUpdates) =>
|
||||
runInGuiThread { () =>
|
||||
channelUpdates.foreach { channelUpdate =>
|
||||
log.debug(s"peer channel with id={} has been updated - flags: {} fees: {} {}", channelUpdate.shortChannelId, channelUpdate.channelFlags, channelUpdate.feeBaseMsat, channelUpdate.feeProportionalMillionths)
|
||||
if (mainController.networkChannelsMap.containsKey(channelUpdate.shortChannelId)) {
|
||||
val c = mainController.networkChannelsMap.get(channelUpdate.shortChannelId)
|
||||
if (channelUpdate.channelFlags.isNode1) {
|
||||
c.isNode1Enabled = Some(channelUpdate.channelFlags.isEnabled)
|
||||
c.feeBaseMsatNode1_opt = Some(channelUpdate.feeBaseMsat.toLong)
|
||||
c.feeProportionalMillionthsNode1_opt = Some(channelUpdate.feeProportionalMillionths)
|
||||
} else {
|
||||
c.isNode2Enabled = Some(channelUpdate.channelFlags.isEnabled)
|
||||
c.feeBaseMsatNode2_opt = Some(channelUpdate.feeBaseMsat.toLong)
|
||||
c.feeProportionalMillionthsNode2_opt = Some(channelUpdate.feeProportionalMillionths)
|
||||
}
|
||||
mainController.networkChannelsMap.put(channelUpdate.shortChannelId, c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
case p: PaymentFailed =>
|
||||
val distilledFailures = PaymentFailure.transformForUser(p.failures)
|
||||
val message = s"${distilledFailures.size} attempts:\n${
|
||||
distilledFailures.map {
|
||||
case LocalFailure(_, t) => s"- (local) ${t.getMessage}"
|
||||
case RemoteFailure(_, e) => s"- (remote) ${e.failureMessage.message}"
|
||||
case _ => "- Unknown error"
|
||||
}.mkString("\n")
|
||||
}"
|
||||
mainController.handlers.notification("Payment Failed", message, NOTIFICATION_ERROR)
|
||||
|
||||
case p: PaymentSent =>
|
||||
log.debug(s"payment sent with h=${p.paymentHash}, amount=${p.recipientAmount}, fees=${p.feesPaid}")
|
||||
val message = CoinUtils.formatAmountInUnit(p.amountWithFees, FxApp.getUnit, withUnit = true)
|
||||
mainController.handlers.notification("Payment Sent", message, NOTIFICATION_SUCCESS)
|
||||
runInGuiThread(() => mainController.paymentSentList.add(PaymentSentRecord(p, LocalDateTime.now())))
|
||||
|
||||
case p: PaymentReceived =>
|
||||
log.debug(s"payment received with h=${p.paymentHash}, amount=${p.amount}")
|
||||
runInGuiThread(() => mainController.paymentReceivedList.add(PaymentReceivedRecord(p, LocalDateTime.now())))
|
||||
|
||||
case p: PaymentRelayed =>
|
||||
log.debug(s"payment relayed with h=${p.paymentHash}, amount=${p.amountIn}, feesEarned=${p.amountOut}")
|
||||
runInGuiThread(() => mainController.paymentRelayedList.add(PaymentRelayedRecord(p, LocalDateTime.now())))
|
||||
|
||||
case ZMQConnected =>
|
||||
log.debug("ZMQ connection UP")
|
||||
runInGuiThread(() => mainController.hideBlockerModal)
|
||||
|
||||
case ZMQDisconnected =>
|
||||
log.debug("ZMQ connection DOWN")
|
||||
runInGuiThread(() => mainController.showBlockerModal("Bitcoin Core"))
|
||||
|
||||
}
|
||||
}
|
@ -1,134 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui
|
||||
|
||||
import java.util.UUID
|
||||
import akka.pattern.{AskTimeoutException, ask}
|
||||
import akka.util.Timeout
|
||||
import fr.acinq.eclair.blockchain.fee.FeeratePerKB
|
||||
import fr.acinq.eclair.gui.controllers._
|
||||
import fr.acinq.eclair.io.{NodeURI, Peer}
|
||||
import fr.acinq.eclair.payment._
|
||||
import fr.acinq.eclair.payment.receive.MultiPartHandler.ReceivePayment
|
||||
import fr.acinq.eclair.payment.send.PaymentInitiator.SendPaymentToNode
|
||||
import fr.acinq.eclair.router.RouteCalculation
|
||||
import fr.acinq.eclair.{MilliSatoshi, _}
|
||||
import grizzled.slf4j.Logging
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent.{ExecutionContext, Future}
|
||||
import scala.util.{Failure, Success}
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class Handlers(fKit: Future[Kit])(implicit ec: ExecutionContext = ExecutionContext.Implicits.global) extends Logging {
|
||||
|
||||
implicit val timeout = Timeout(60 seconds)
|
||||
|
||||
private var notifsController: Option[NotificationsController] = None
|
||||
|
||||
def initNotifications(controller: NotificationsController): Unit = {
|
||||
notifsController = Option(controller)
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens a connection to a node. If the channel option exists this will also open a channel with the node, with a
|
||||
* `fundingSatoshis` capacity and `pushMsat` amount.
|
||||
*/
|
||||
def open(nodeUri: NodeURI, channel: Option[Peer.OpenChannel]): Unit = {
|
||||
logger.info(s"opening a connection to nodeUri=$nodeUri")
|
||||
(for {
|
||||
kit <- fKit
|
||||
conn <- kit.switchboard ? Peer.Connect(nodeUri)
|
||||
} yield (kit, conn)) onComplete {
|
||||
case Success((k, _)) =>
|
||||
logger.info(s"connection to $nodeUri successful")
|
||||
channel match {
|
||||
case Some(openChannel) =>
|
||||
k.switchboard ? openChannel onComplete {
|
||||
case Success(s) =>
|
||||
logger.info(s"successfully opened channel $s")
|
||||
notification("Channel created", s.toString, NOTIFICATION_SUCCESS)
|
||||
case Failure(_: AskTimeoutException) =>
|
||||
logger.info("opening channel is taking a long time, notifications will not be shown")
|
||||
case Failure(t) =>
|
||||
logger.info("could not open channel ", t)
|
||||
notification("Channel creation failed", t.getMessage, NOTIFICATION_ERROR)
|
||||
}
|
||||
case None =>
|
||||
}
|
||||
case Failure(t) =>
|
||||
logger.error(s"could not create connection to $nodeUri ", t)
|
||||
notification("Connection failed", t.getMessage, NOTIFICATION_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
def send(overrideAmountMsat_opt: Option[Long], req: PaymentRequest) = {
|
||||
val amountMsat = overrideAmountMsat_opt
|
||||
.orElse(req.amount.map(_.toLong))
|
||||
.getOrElse(throw new RuntimeException("you need to manually specify an amount for this payment request"))
|
||||
logger.info(s"sending $amountMsat to ${req.paymentHash} @ ${req.nodeId}")
|
||||
(for {
|
||||
kit <- fKit
|
||||
routeParams = kit.nodeParams.routerConf.pathFindingExperimentConf.getRandomConf().getDefaultRouteParams
|
||||
sendPayment = req.minFinalCltvExpiryDelta match {
|
||||
case None => SendPaymentToNode(MilliSatoshi(amountMsat), req, kit.nodeParams.maxPaymentAttempts, assistedRoutes = req.routingInfo, routeParams = routeParams)
|
||||
case Some(minFinalCltvExpiry) => SendPaymentToNode(MilliSatoshi(amountMsat), req, kit.nodeParams.maxPaymentAttempts, assistedRoutes = req.routingInfo, fallbackFinalExpiryDelta = minFinalCltvExpiry, routeParams = routeParams)
|
||||
}
|
||||
res <- (kit.paymentInitiator ? sendPayment).mapTo[UUID]
|
||||
} yield res).recover {
|
||||
// completed payment will be handled by the GUIUpdater by listening to PaymentSucceeded/PaymentFailed events
|
||||
case _: AskTimeoutException =>
|
||||
logger.info("sending payment is taking a long time, notifications will not be shown")
|
||||
case t =>
|
||||
val message = t.getMessage
|
||||
notification("Payment Failed", message, NOTIFICATION_ERROR)
|
||||
}
|
||||
}
|
||||
|
||||
def receive(amountMsat_opt: Option[MilliSatoshi], description: String): Future[String] = for {
|
||||
kit <- fKit
|
||||
res <- (kit.paymentHandler ? ReceivePayment(amountMsat_opt, Left(description))).mapTo[PaymentRequest].map(PaymentRequest.write)
|
||||
} yield res
|
||||
|
||||
/**
|
||||
* Displays a system notification if the system supports it.
|
||||
*
|
||||
* @param title Title of the notification
|
||||
* @param message main message of the notification, will not wrap
|
||||
* @param notificationType type of the message, default to NONE
|
||||
* @param showAppName true if you want the notification title to be preceded by "Eclair - ". True by default
|
||||
*/
|
||||
def notification(title: String, message: String, notificationType: NotificationType = NOTIFICATION_NONE, showAppName: Boolean = true): Unit = {
|
||||
notifsController.foreach(_.addNotification(if (showAppName) s"Eclair - $title" else title, message, notificationType))
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves on-chain fees for a funding transaction, using the funding block target set in the config file.
|
||||
*
|
||||
* @return Future containing a Long in satoshi per kilobyte
|
||||
*/
|
||||
def getFundingFeeRatePerKb(): Future[FeeratePerKB] = {
|
||||
for {
|
||||
kit <- fKit
|
||||
ratePerKw = {
|
||||
kit.nodeParams.onChainFeeConf.feeEstimator.getFeeratePerKb(target = kit.nodeParams.onChainFeeConf.feeTargets.fundingBlockTarget)
|
||||
}
|
||||
} yield ratePerKw
|
||||
}
|
||||
}
|
@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.application.HostServices
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.text.Text
|
||||
|
||||
/**
|
||||
* Created by DPA on 28/09/2016.
|
||||
*/
|
||||
class AboutController(hostServices: HostServices) extends Logging {
|
||||
|
||||
@FXML var version: Text = _
|
||||
|
||||
@FXML def initialize = {
|
||||
version.setText(getClass.getPackage.getImplementationVersion)
|
||||
}
|
||||
|
||||
@FXML def openApacheLicencePage = hostServices.showDocument("https://www.apache.org/licenses/LICENSE-2.0")
|
||||
|
||||
@FXML def openACINQPage = hostServices.showDocument("https://acinq.co")
|
||||
|
||||
@FXML def openGithubPage = hostServices.showDocument("https://github.com/ACINQ/eclair")
|
||||
|
||||
@FXML def openLNRFCPage = hostServices.showDocument("https://github.com/lightningnetwork/lightning-rfc")
|
||||
}
|
@ -1,153 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import akka.actor.ActorRef
|
||||
import com.google.common.base.Strings
|
||||
import fr.acinq.eclair._
|
||||
import fr.acinq.eclair.channel.{CMD_CLOSE, CMD_FORCECLOSE, AbstractCommitments, Commitments}
|
||||
import fr.acinq.eclair.gui.FxApp
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.application.Platform
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.Alert.AlertType
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.input.{ContextMenuEvent, MouseEvent}
|
||||
import javafx.scene.layout.VBox
|
||||
|
||||
/**
|
||||
* Created by DPA on 23/09/2016.
|
||||
*/
|
||||
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: Label = _
|
||||
@FXML var nodeAlias: Label = _
|
||||
@FXML var nodeId: TextField = _
|
||||
@FXML var state: TextField = _
|
||||
@FXML var funder: TextField = _
|
||||
@FXML var close: Button = _
|
||||
@FXML var forceclose: Button = _
|
||||
|
||||
private var contextMenu: ContextMenu = _
|
||||
private var balance: MilliSatoshi = MilliSatoshi(0)
|
||||
private var capacity: MilliSatoshi = MilliSatoshi(0)
|
||||
|
||||
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", 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)
|
||||
funder.setContextMenu(contextMenu)
|
||||
state.setContextMenu(contextMenu)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@FXML def initialize() = {
|
||||
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(ActorRef.noSender, scriptPubKey = None, feerates = 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(ActorRef.noSender)
|
||||
}
|
||||
}
|
||||
})
|
||||
buildChannelContextMenu()
|
||||
}
|
||||
|
||||
@FXML def openChannelContext(event: ContextMenuEvent): Unit = {
|
||||
contextMenu.show(channelId, event.getScreenX, event.getScreenY)
|
||||
event.consume()
|
||||
}
|
||||
|
||||
@FXML def closeChannelContext(event: MouseEvent): Unit = {
|
||||
if (contextMenu != null) contextMenu.hide()
|
||||
}
|
||||
|
||||
def updateRemoteNodeAlias(alias: String): Unit = {
|
||||
nodeAlias.setText(alias)
|
||||
nodeAlias.setVisible(!Strings.isNullOrEmpty(alias))
|
||||
}
|
||||
|
||||
def updateBalance(commitments: Commitments): Unit = {
|
||||
balance = commitments.localCommit.spec.toLocal
|
||||
capacity = commitments.capacity.toMilliSatoshi
|
||||
}
|
||||
|
||||
def refreshBalance(): Unit = {
|
||||
amountUs.setText(s"${CoinUtils.formatAmountInUnit(balance, FxApp.getUnit, false)} / ${CoinUtils.formatAmountInUnit(capacity, FxApp.getUnit, withUnit = true)}")
|
||||
balanceBar.setProgress(balance.toLong.toDouble / capacity.toLong)
|
||||
}
|
||||
|
||||
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}
|
||||
|"""
|
||||
}
|
@ -1,577 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
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.Crypto.PublicKey
|
||||
import fr.acinq.bitcoin.Satoshi
|
||||
import fr.acinq.eclair.NodeParams.BITCOIND
|
||||
import fr.acinq.eclair.gui.stages._
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, CopyAction, IndexedObservableList}
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.payment.{PaymentEvent, PaymentReceived, PaymentRelayed, PaymentSent}
|
||||
import fr.acinq.eclair.wire.protocol.{ChannelAnnouncement, NodeAnnouncement}
|
||||
import fr.acinq.eclair.{CoinUtils, MilliSatoshi, Setup, ShortChannelId}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.animation.{FadeTransition, ParallelTransition, SequentialTransition, TranslateTransition}
|
||||
import javafx.application.{HostServices, Platform}
|
||||
import javafx.beans.property._
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.collections.ListChangeListener.Change
|
||||
import javafx.collections.{FXCollections, ListChangeListener, ObservableList}
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.TableColumn.CellDataFeatures
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.image.{Image, ImageView}
|
||||
import javafx.scene.input.ContextMenuEvent
|
||||
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._
|
||||
import javafx.util.{Callback, Duration}
|
||||
|
||||
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],
|
||||
capacity: Satoshi, var isNode1Enabled: Option[Boolean], var isNode2Enabled: Option[Boolean])
|
||||
|
||||
sealed trait Record {
|
||||
val event: PaymentEvent
|
||||
val date: LocalDateTime
|
||||
}
|
||||
|
||||
case class PaymentSentRecord(event: PaymentSent, date: LocalDateTime) extends Record
|
||||
|
||||
case class PaymentReceivedRecord(event: PaymentReceived, date: LocalDateTime) extends Record
|
||||
|
||||
case class PaymentRelayedRecord(event: PaymentRelayed, date: LocalDateTime) extends Record
|
||||
|
||||
/**
|
||||
* Created by DPA on 22/09/2016.
|
||||
*/
|
||||
class MainController(val handlers: Handlers, val hostServices: HostServices) extends Logging {
|
||||
|
||||
@FXML var root: AnchorPane = _
|
||||
var contextMenu: ContextMenu = _
|
||||
|
||||
// menu
|
||||
@FXML var menuOpen: MenuItem = _
|
||||
@FXML var menuSend: MenuItem = _
|
||||
@FXML var menuReceive: MenuItem = _
|
||||
|
||||
// status bar elements
|
||||
@FXML var labelNodeId: Label = _
|
||||
@FXML var statusBalanceLabel: Label = _
|
||||
@FXML var rectRGB: Rectangle = _
|
||||
@FXML var labelAlias: Label = _
|
||||
@FXML var labelApi: Label = _
|
||||
@FXML var labelServer: Label = _
|
||||
@FXML var bitcoinWallet: Label = _
|
||||
@FXML var bitcoinChain: Label = _
|
||||
|
||||
// channels tab elements
|
||||
@FXML var channelInfo: VBox = _
|
||||
@FXML var channelBox: VBox = _
|
||||
@FXML var channelsTab: Tab = _
|
||||
|
||||
// all nodes tab
|
||||
val networkNodesMap = new IndexedObservableList[PublicKey, NodeAnnouncement]
|
||||
private val networkNodesList = networkNodesMap.list
|
||||
@FXML var networkNodesTab: Tab = _
|
||||
@FXML var networkNodesTable: TableView[NodeAnnouncement] = _
|
||||
@FXML var networkNodesIdColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
@FXML var networkNodesAliasColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
@FXML var networkNodesRGBColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
@FXML var networkNodesIPColumn: TableColumn[NodeAnnouncement, String] = _
|
||||
|
||||
// all channels
|
||||
val networkChannelsMap = new IndexedObservableList[ShortChannelId, ChannelInfo]
|
||||
private val networkChannelsList = networkChannelsMap.list
|
||||
@FXML var networkChannelsTab: Tab = _
|
||||
@FXML var networkChannelsTable: TableView[ChannelInfo] = _
|
||||
@FXML var networkChannelsIdColumn: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsNode1Column: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsDirectionsColumn: TableColumn[ChannelInfo, ChannelInfo] = _
|
||||
@FXML var networkChannelsNode2Column: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsFeeBaseMsatNode1Column: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsFeeProportionalMillionthsNode1Column: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsFeeBaseMsatNode2Column: TableColumn[ChannelInfo, String] = _
|
||||
@FXML var networkChannelsFeeProportionalMillionthsNode2Column: TableColumn[ChannelInfo, String] = _
|
||||
|
||||
@FXML var networkChannelsCapacityColumn: TableColumn[ChannelInfo, String] = _
|
||||
|
||||
// payment sent table
|
||||
val paymentSentList = FXCollections.observableArrayList[PaymentSentRecord]()
|
||||
@FXML var paymentSentTab: Tab = _
|
||||
@FXML var paymentSentTable: TableView[PaymentSentRecord] = _
|
||||
@FXML var paymentSentAmountColumn: TableColumn[PaymentSentRecord, String] = _
|
||||
@FXML var paymentSentFeesColumn: TableColumn[PaymentSentRecord, String] = _
|
||||
@FXML var paymentSentHashColumn: TableColumn[PaymentSentRecord, String] = _
|
||||
@FXML var paymentSentPreimageColumn: TableColumn[PaymentSentRecord, String] = _
|
||||
@FXML var paymentSentDateColumn: TableColumn[PaymentSentRecord, String] = _
|
||||
|
||||
// payment received table
|
||||
val paymentReceivedList = FXCollections.observableArrayList[PaymentReceivedRecord]()
|
||||
@FXML var paymentReceivedTab: Tab = _
|
||||
@FXML var paymentReceivedTable: TableView[PaymentReceivedRecord] = _
|
||||
@FXML var paymentReceivedAmountColumn: TableColumn[PaymentReceivedRecord, String] = _
|
||||
@FXML var paymentReceivedHashColumn: TableColumn[PaymentReceivedRecord, String] = _
|
||||
@FXML var paymentReceivedDateColumn: TableColumn[PaymentReceivedRecord, String] = _
|
||||
|
||||
// payment relayed table
|
||||
val paymentRelayedList = FXCollections.observableArrayList[PaymentRelayedRecord]()
|
||||
@FXML var paymentRelayedTab: Tab = _
|
||||
@FXML var paymentRelayedTable: TableView[PaymentRelayedRecord] = _
|
||||
@FXML var paymentRelayedAmountColumn: TableColumn[PaymentRelayedRecord, String] = _
|
||||
@FXML var paymentRelayedFeesColumn: TableColumn[PaymentRelayedRecord, String] = _
|
||||
@FXML var paymentRelayedHashColumn: TableColumn[PaymentRelayedRecord, String] = _
|
||||
@FXML var paymentRelayedDateColumn: TableColumn[PaymentRelayedRecord, String] = _
|
||||
|
||||
@FXML var blocker: StackPane = _
|
||||
@FXML var blockerDialog: HBox = _
|
||||
@FXML var blockerDialogTitleEngineName: Text = _
|
||||
|
||||
val PAYMENT_DATE_FORMAT: DateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
|
||||
val moneyFormatter: NumberFormat = NumberFormat.getInstance(Locale.getDefault)
|
||||
|
||||
/**
|
||||
* Initialize the main window.
|
||||
*
|
||||
* - Set content in status bar labels (node id, host, ...)
|
||||
* - init the channels tab with a 'No channels found' message
|
||||
* - init the 'nodes in network' and 'channels in network' tables
|
||||
*/
|
||||
@FXML def initialize(): Unit = {
|
||||
|
||||
// init channels tab
|
||||
if (channelBox.getChildren.size() > 0) {
|
||||
channelInfo.setScaleY(0)
|
||||
channelInfo.setOpacity(0)
|
||||
}
|
||||
channelBox.heightProperty().addListener(new ChangeListener[Number] {
|
||||
override def changed(observable: ObservableValue[_ <: Number], oldValue: Number, newValue: Number): Unit = {
|
||||
if (channelBox.getChildren.size() > 0) {
|
||||
channelInfo.setScaleY(0)
|
||||
channelInfo.setOpacity(0)
|
||||
} else {
|
||||
channelInfo.setScaleY(1)
|
||||
channelInfo.setOpacity(1)
|
||||
}
|
||||
channelsTab.setText(s"Local Channels (${channelBox.getChildren.size})")
|
||||
}
|
||||
})
|
||||
|
||||
// init all nodes
|
||||
networkNodesTable.setItems(networkNodesList)
|
||||
networkNodesList.addListener(new ListChangeListener[NodeAnnouncement] {
|
||||
override def onChanged(c: Change[_ <: NodeAnnouncement]) = updateTabHeader(networkNodesTab, "All Nodes", networkNodesList)
|
||||
})
|
||||
networkNodesIdColumn.setCellValueFactory(new Callback[CellDataFeatures[NodeAnnouncement, String], ObservableValue[String]]() {
|
||||
def call(pn: CellDataFeatures[NodeAnnouncement, String]) = new SimpleStringProperty(pn.getValue.nodeId.toString)
|
||||
})
|
||||
networkNodesAliasColumn.setCellValueFactory(new Callback[CellDataFeatures[NodeAnnouncement, String], ObservableValue[String]]() {
|
||||
def call(pn: CellDataFeatures[NodeAnnouncement, String]) = new SimpleStringProperty(pn.getValue.alias)
|
||||
})
|
||||
networkNodesRGBColumn.setCellValueFactory(new Callback[CellDataFeatures[NodeAnnouncement, String], ObservableValue[String]]() {
|
||||
def call(pn: CellDataFeatures[NodeAnnouncement, String]) = new SimpleStringProperty(pn.getValue.rgbColor.toString)
|
||||
})
|
||||
networkNodesIPColumn.setCellValueFactory(new Callback[CellDataFeatures[NodeAnnouncement, String], ObservableValue[String]]() {
|
||||
def call(pn: CellDataFeatures[NodeAnnouncement, String]) = {
|
||||
val address = pn.getValue.addresses.map(a => HostAndPort.fromParts(a.socketAddress.getHostString, a.socketAddress.getPort)).mkString(",")
|
||||
new SimpleStringProperty(address)
|
||||
}
|
||||
})
|
||||
networkNodesRGBColumn.setCellFactory(new Callback[TableColumn[NodeAnnouncement, String], TableCell[NodeAnnouncement, String]]() {
|
||||
def call(pn: TableColumn[NodeAnnouncement, String]) = {
|
||||
new TableCell[NodeAnnouncement, String]() {
|
||||
override def updateItem(item: String, empty: Boolean): Unit = {
|
||||
super.updateItem(item, empty)
|
||||
if (empty || item == null) {
|
||||
setText(null)
|
||||
setGraphic(null)
|
||||
setStyle(null)
|
||||
} else {
|
||||
setStyle("-fx-background-color:" + item)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
networkNodesTable.setRowFactory(new Callback[TableView[NodeAnnouncement], TableRow[NodeAnnouncement]]() {
|
||||
override def call(table: TableView[NodeAnnouncement]) = setupPeerNodeContextMenu()
|
||||
})
|
||||
|
||||
// init all channels
|
||||
networkChannelsTable.setItems(networkChannelsList)
|
||||
networkChannelsList.addListener(new ListChangeListener[ChannelInfo] {
|
||||
override def onChanged(c: Change[_ <: ChannelInfo]) = updateTabHeader(networkChannelsTab, "All Channels", networkChannelsList)
|
||||
})
|
||||
networkChannelsIdColumn.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(pc.getValue.announcement.shortChannelId.toString)
|
||||
})
|
||||
networkChannelsNode1Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(pc.getValue.announcement.nodeId1.toString)
|
||||
})
|
||||
networkChannelsNode2Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(pc.getValue.announcement.nodeId2.toString)
|
||||
})
|
||||
networkChannelsFeeBaseMsatNode1Column.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, String], ObservableValue[String]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, String]) = new SimpleStringProperty(
|
||||
pc.getValue.feeBaseMsatNode1_opt.map(f => CoinUtils.formatAmountInUnit(MilliSatoshi(f), FxApp.getUnit, withUnit = true)).getOrElse("?"))
|
||||
})
|
||||
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("?"))
|
||||
})
|
||||
// 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"${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"${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(
|
||||
CoinUtils.formatAmountInUnit(pc.getValue.capacity, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
|
||||
networkChannelsTable.setRowFactory(new Callback[TableView[ChannelInfo], TableRow[ChannelInfo]]() {
|
||||
override def call(table: TableView[ChannelInfo]): TableRow[ChannelInfo] = setupPeerChannelContextMenu()
|
||||
})
|
||||
networkChannelsDirectionsColumn.setCellValueFactory(new Callback[CellDataFeatures[ChannelInfo, ChannelInfo], ObservableValue[ChannelInfo]]() {
|
||||
def call(pc: CellDataFeatures[ChannelInfo, ChannelInfo]) = new SimpleObjectProperty[ChannelInfo](pc.getValue)
|
||||
})
|
||||
networkChannelsDirectionsColumn.setCellFactory(new Callback[TableColumn[ChannelInfo, ChannelInfo], TableCell[ChannelInfo, ChannelInfo]]() {
|
||||
def call(pn: TableColumn[ChannelInfo, ChannelInfo]) = {
|
||||
new TableCell[ChannelInfo, ChannelInfo]() {
|
||||
val directionImage = new ImageView
|
||||
directionImage.setFitWidth(20)
|
||||
directionImage.setFitHeight(20)
|
||||
|
||||
override def updateItem(item: ChannelInfo, empty: Boolean): Unit = {
|
||||
super.updateItem(item, empty)
|
||||
if (item == null || empty) {
|
||||
setGraphic(null)
|
||||
setText(null)
|
||||
} else {
|
||||
item match {
|
||||
case ChannelInfo(_, _, _, _, _, _, Some(true), Some(true)) =>
|
||||
directionImage.setImage(new Image("/gui/commons/images/in-out-11.png", false))
|
||||
setTooltip(new Tooltip("Both Node 1 and Node 2 are enabled"))
|
||||
setGraphic(directionImage)
|
||||
case ChannelInfo(_, _, _, _, _, _, Some(true), Some(false)) =>
|
||||
directionImage.setImage(new Image("/gui/commons/images/in-out-10.png", false))
|
||||
setTooltip(new Tooltip("Node 1 is enabled, but not Node 2"))
|
||||
setGraphic(directionImage)
|
||||
case ChannelInfo(_, _, _, _, _, _, Some(false), Some(true)) =>
|
||||
directionImage.setImage(new Image("/gui/commons/images/in-out-01.png", false))
|
||||
setTooltip(new Tooltip("Node 2 is enabled, but not Node 1"))
|
||||
setGraphic(directionImage)
|
||||
case ChannelInfo(_, _, _, _, _, _, Some(false), Some(false)) =>
|
||||
directionImage.setImage(new Image("/gui/commons/images/in-out-00.png", false))
|
||||
setTooltip(new Tooltip("Neither Node 1 nor Node 2 is enabled"))
|
||||
setGraphic(directionImage)
|
||||
case _ =>
|
||||
setText("?")
|
||||
setGraphic(null)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// init payment sent
|
||||
paymentSentTable.setItems(paymentSentList)
|
||||
paymentSentList.addListener(new ListChangeListener[PaymentSentRecord] {
|
||||
override def onChanged(c: Change[_ <: PaymentSentRecord]) = updateTabHeader(paymentSentTab, "Sent", paymentSentList)
|
||||
})
|
||||
paymentSentAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentSentRecord, String], ObservableValue[String]]() {
|
||||
def call(record: CellDataFeatures[PaymentSentRecord, String]) = new SimpleStringProperty(CoinUtils.formatAmountInUnit(record.getValue.event.recipientAmount, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
paymentSentFeesColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentSentRecord, String], ObservableValue[String]]() {
|
||||
def call(record: CellDataFeatures[PaymentSentRecord, String]) = new SimpleStringProperty(CoinUtils.formatAmountInUnit(record.getValue.event.feesPaid, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
paymentSentHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentSentPreimageColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentSentRecord, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[PaymentSentRecord, String]) = new SimpleStringProperty(p.getValue.event.paymentPreimage.toString())
|
||||
})
|
||||
paymentSentDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentSentTable.setRowFactory(paymentSentRowFactory)
|
||||
|
||||
// init payment received
|
||||
paymentReceivedTable.setItems(paymentReceivedList)
|
||||
paymentReceivedList.addListener(new ListChangeListener[PaymentReceivedRecord] {
|
||||
override def onChanged(c: Change[_ <: PaymentReceivedRecord]) = updateTabHeader(paymentReceivedTab, "Received", paymentReceivedList)
|
||||
})
|
||||
paymentReceivedAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentReceivedRecord, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[PaymentReceivedRecord, String]) = new SimpleStringProperty(CoinUtils.formatAmountInUnit(p.getValue.event.amount, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
paymentReceivedHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentReceivedDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentReceivedTable.setRowFactory(paymentRowFactory)
|
||||
|
||||
// init payment relayed
|
||||
paymentRelayedTable.setItems(paymentRelayedList)
|
||||
paymentRelayedList.addListener(new ListChangeListener[PaymentRelayedRecord] {
|
||||
override def onChanged(c: Change[_ <: PaymentRelayedRecord]) = updateTabHeader(paymentRelayedTab, "Relayed", paymentRelayedList)
|
||||
})
|
||||
paymentRelayedAmountColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentRelayedRecord, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[PaymentRelayedRecord, String]) = new SimpleStringProperty(CoinUtils.formatAmountInUnit(p.getValue.event.amountIn, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
paymentRelayedFeesColumn.setCellValueFactory(new Callback[CellDataFeatures[PaymentRelayedRecord, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[PaymentRelayedRecord, String]) = new SimpleStringProperty(CoinUtils.formatAmountInUnit(
|
||||
p.getValue.event.amountIn - p.getValue.event.amountOut, FxApp.getUnit, withUnit = true))
|
||||
})
|
||||
paymentRelayedHashColumn.setCellValueFactory(paymentHashCellValueFactory)
|
||||
paymentRelayedDateColumn.setCellValueFactory(paymentDateCellValueFactory)
|
||||
paymentRelayedTable.setRowFactory(paymentRowFactory)
|
||||
}
|
||||
|
||||
def initInfoFields(setup: Setup) = {
|
||||
// init status bar
|
||||
labelNodeId.setText(s"${setup.nodeParams.nodeId}")
|
||||
labelAlias.setText(s"${setup.nodeParams.alias}")
|
||||
rectRGB.setFill(Color.web(setup.nodeParams.color.toString))
|
||||
labelApi.setText(s"${setup.config.getInt("api.port")}")
|
||||
labelServer.setText(s"${setup.config.getInt("server.port")}")
|
||||
|
||||
bitcoinWallet.setText("Bitcoin-core")
|
||||
bitcoinChain.setText(s"${setup.chain.toUpperCase()}")
|
||||
bitcoinChain.getStyleClass.add(setup.chain)
|
||||
|
||||
val nodeURI_opt = setup.nodeParams.publicAddresses.headOption.map(address => {
|
||||
s"${setup.nodeParams.nodeId}@${HostAndPort.fromParts(address.socketAddress.getHostString, address.socketAddress.getPort)}"
|
||||
})
|
||||
|
||||
contextMenu = ContextMenuUtils.buildCopyContext(List(CopyAction("Copy Pubkey", setup.nodeParams.nodeId.toString())))
|
||||
nodeURI_opt.map(nodeURI => {
|
||||
val nodeInfoAction = new MenuItem("Node Info")
|
||||
nodeInfoAction.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = {
|
||||
val nodeInfoStage = new NodeInfoStage(nodeURI, handlers)
|
||||
nodeInfoStage.initOwner(getWindow.orNull)
|
||||
positionAtCenter(nodeInfoStage)
|
||||
nodeInfoStage.show()
|
||||
}
|
||||
})
|
||||
contextMenu.getItems.add(ContextMenuUtils.buildCopyMenuItem(CopyAction("Copy URI", nodeURI)))
|
||||
contextMenu.getItems.add(nodeInfoAction)
|
||||
})
|
||||
}
|
||||
|
||||
private def updateTabHeader(tab: Tab, prefix: String, items: ObservableList[_]) = Platform.runLater(new Runnable() {
|
||||
override def run(): Unit = tab.setText(s"$prefix (${items.size})")
|
||||
})
|
||||
|
||||
private def paymentHashCellValueFactory[T <: Record] = new Callback[CellDataFeatures[T, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[T, String]) = new SimpleStringProperty(p.getValue.event.paymentHash.toString)
|
||||
}
|
||||
|
||||
private def paymentDateCellValueFactory[T <: Record] = new Callback[CellDataFeatures[T, String], ObservableValue[String]]() {
|
||||
def call(p: CellDataFeatures[T, String]) = new SimpleStringProperty(p.getValue.date.format(PAYMENT_DATE_FORMAT))
|
||||
}
|
||||
|
||||
private def paymentSentRowFactory = new Callback[TableView[PaymentSentRecord], TableRow[PaymentSentRecord]]() {
|
||||
override def call(table: TableView[PaymentSentRecord]): TableRow[PaymentSentRecord] = {
|
||||
val row = new TableRow[PaymentSentRecord]
|
||||
val rowContextMenu = new ContextMenu
|
||||
val copyHash = new MenuItem("Copy Payment Hash")
|
||||
copyHash.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(p) => ContextMenuUtils.copyToClipboard(p.event.paymentHash.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
val copyPreimage = new MenuItem("Copy Payment Preimage")
|
||||
copyPreimage.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(p) => ContextMenuUtils.copyToClipboard(p.event.paymentPreimage.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
rowContextMenu.getItems.addAll(copyHash, copyPreimage)
|
||||
row.setContextMenu(rowContextMenu)
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
private def paymentRowFactory[T <: Record] = new Callback[TableView[T], TableRow[T]]() {
|
||||
override def call(table: TableView[T]): TableRow[T] = {
|
||||
val row = new TableRow[T]
|
||||
val rowContextMenu = new ContextMenu
|
||||
val copyHash = new MenuItem("Copy Payment Hash")
|
||||
copyHash.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(p) => ContextMenuUtils.copyToClipboard(p.event.paymentHash.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
rowContextMenu.getItems.addAll(copyHash)
|
||||
row.setContextMenu(rowContextMenu)
|
||||
row
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a row for a node with context actions (copy node uri and id).
|
||||
*
|
||||
* @return TableRow the created row
|
||||
*/
|
||||
private def setupPeerNodeContextMenu(): TableRow[NodeAnnouncement] = {
|
||||
val row = new TableRow[NodeAnnouncement]
|
||||
val rowContextMenu = new ContextMenu
|
||||
val copyPubkey = new MenuItem("Copy Pubkey")
|
||||
copyPubkey.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(pn) => ContextMenuUtils.copyToClipboard(pn.nodeId.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
val copyURI = new MenuItem("Copy first known URI")
|
||||
copyURI.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(pn) => ContextMenuUtils.copyToClipboard(
|
||||
pn.addresses.headOption match {
|
||||
case Some(firstAddress) => s"${pn.nodeId.toString}@${HostAndPort.fromParts(firstAddress.socketAddress.getHostString, firstAddress.socketAddress.getPort)}"
|
||||
case None => "no URI Known"
|
||||
})
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
rowContextMenu.getItems.addAll(copyPubkey, copyURI)
|
||||
row.setContextMenu(rowContextMenu)
|
||||
row
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a row for a PeerChannel with Copy context actions.
|
||||
*
|
||||
* @return TableRow the created row
|
||||
*/
|
||||
private def setupPeerChannelContextMenu(): TableRow[ChannelInfo] = {
|
||||
val row = new TableRow[ChannelInfo]
|
||||
val rowContextMenu = new ContextMenu
|
||||
val copyChannelId = new MenuItem("Copy Channel Id")
|
||||
copyChannelId.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(pc) => ContextMenuUtils.copyToClipboard(pc.announcement.shortChannelId.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
val copyNode1 = new MenuItem("Copy Node 1")
|
||||
copyNode1.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(pc) => ContextMenuUtils.copyToClipboard(pc.announcement.nodeId1.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
val copyNode2 = new MenuItem("Copy Node 2")
|
||||
copyNode2.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = Option(row.getItem) match {
|
||||
case Some(pc) => ContextMenuUtils.copyToClipboard(pc.announcement.nodeId2.toString)
|
||||
case None =>
|
||||
}
|
||||
})
|
||||
rowContextMenu.getItems.addAll(copyChannelId, copyNode1, copyNode2)
|
||||
row.setContextMenu(rowContextMenu)
|
||||
row
|
||||
}
|
||||
|
||||
@FXML def handleOpenChannel() = {
|
||||
val openChannelStage = new OpenChannelStage(handlers)
|
||||
openChannelStage.initOwner(getWindow.orNull)
|
||||
positionAtCenter(openChannelStage)
|
||||
openChannelStage.show()
|
||||
}
|
||||
|
||||
@FXML def handleSendPayment() = {
|
||||
val sendPaymentStage = new SendPaymentStage(handlers)
|
||||
sendPaymentStage.initOwner(getWindow.orNull)
|
||||
positionAtCenter(sendPaymentStage)
|
||||
sendPaymentStage.show()
|
||||
}
|
||||
|
||||
@FXML def handleReceivePayment() = {
|
||||
val receiveStage = new ReceivePaymentStage(handlers)
|
||||
receiveStage.initOwner(getWindow.orNull)
|
||||
positionAtCenter(receiveStage)
|
||||
receiveStage.show()
|
||||
}
|
||||
|
||||
def showBlockerModal(backendName: String) = {
|
||||
blockerDialogTitleEngineName.setText(backendName)
|
||||
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(): Unit = {
|
||||
val aboutStage = new AboutStage(hostServices)
|
||||
aboutStage.initOwner(getWindow.getOrElse(null))
|
||||
positionAtCenter(aboutStage)
|
||||
aboutStage.show()
|
||||
}
|
||||
|
||||
@FXML def openNodeIdContext(event: ContextMenuEvent) = contextMenu.show(labelNodeId, event.getScreenX, event.getScreenY)
|
||||
|
||||
def positionAtCenter(childStage: Stage): Unit = {
|
||||
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))
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.utils.{ContextMenuUtils, QRCodeUtils}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.stage.Stage
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
class NodeInfoController(val address: String, val handlers: Handlers, val stage: Stage) extends Logging {
|
||||
|
||||
@FXML var uriTextarea: TextArea = _
|
||||
@FXML var uriQRCode: ImageView = _
|
||||
|
||||
@FXML def initialize(): Unit = {
|
||||
uriTextarea.setText(address)
|
||||
Try(QRCodeUtils.createQRCode(address)) match {
|
||||
case Success(wImage) => uriQRCode.setImage(wImage)
|
||||
case Failure(t) => logger.debug("Failed to generate URI QR Code", t)
|
||||
}
|
||||
stage.sizeToScene()
|
||||
}
|
||||
|
||||
@FXML def handleCopyURI(event: ActionEvent): Boolean = ContextMenuUtils.copyToClipboard(address)
|
||||
|
||||
@FXML def handleClose(event: ActionEvent): Unit = stage.close()
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{Button, Label}
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.input.MouseEvent
|
||||
import javafx.scene.layout.GridPane
|
||||
|
||||
/**
|
||||
* Created by DPA on 17/02/2017.
|
||||
*/
|
||||
class NotificationPaneController {
|
||||
|
||||
@FXML var rootPane: GridPane = _
|
||||
@FXML var titleLabel: Label = _
|
||||
@FXML var messageLabel: Label = _
|
||||
@FXML var icon: ImageView = _
|
||||
@FXML var closeButton: Button = _
|
||||
@FXML var copyButton: Button = _
|
||||
|
||||
@FXML def handleMouseEnter(event: MouseEvent) = {
|
||||
rootPane.setOpacity(1)
|
||||
}
|
||||
|
||||
@FXML def handleMouseExit(event: MouseEvent) = {
|
||||
rootPane.setOpacity(0.95)
|
||||
}
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import fr.acinq.eclair.gui.utils.ContextMenuUtils
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.animation._
|
||||
import javafx.application.Platform
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.{FXML, FXMLLoader}
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.layout.{GridPane, VBox}
|
||||
import javafx.util.Duration
|
||||
|
||||
sealed trait NotificationType
|
||||
|
||||
case object NOTIFICATION_NONE extends NotificationType
|
||||
|
||||
case object NOTIFICATION_SUCCESS extends NotificationType
|
||||
|
||||
case object NOTIFICATION_ERROR extends NotificationType
|
||||
|
||||
case object NOTIFICATION_INFO extends NotificationType
|
||||
|
||||
/**
|
||||
* Created by DPA on 17/02/2017.
|
||||
*/
|
||||
class NotificationsController extends Logging {
|
||||
@FXML var notifsVBox: VBox = _
|
||||
|
||||
val successIcon: Image = new Image(getClass.getResource("/gui/commons/images/success_icon.png").toExternalForm, true)
|
||||
val errorIcon: Image = new Image(getClass.getResource("/gui/commons/images/warning_icon.png").toExternalForm, true)
|
||||
val infoIcon: Image = new Image(getClass.getResource("/gui/commons/images/info_icon.png").toExternalForm, true)
|
||||
|
||||
/**
|
||||
* Adds a notification panel to the notifications stage
|
||||
*
|
||||
* @param title Title of the notification, should not be too long
|
||||
* @param message Main message of the notification
|
||||
* @param notificationType type of the notification (error, warning, success, info...)
|
||||
*/
|
||||
def addNotification(title: String, message: String, notificationType: NotificationType) = {
|
||||
|
||||
val loader = new FXMLLoader(getClass.getResource("/gui/main/notificationPane.fxml"))
|
||||
val notifPaneController = new NotificationPaneController
|
||||
loader.setController(notifPaneController)
|
||||
|
||||
Platform.runLater(new Runnable() {
|
||||
override def run = {
|
||||
val root = loader.load[GridPane]
|
||||
notifsVBox.getChildren.add(root)
|
||||
|
||||
// set notification content
|
||||
notifPaneController.titleLabel.setText(title)
|
||||
notifPaneController.messageLabel.setText(message.capitalize)
|
||||
val autoDismissed = notificationType match {
|
||||
case NOTIFICATION_SUCCESS => {
|
||||
notifPaneController.rootPane.setStyle("-fx-border-color: #28d087")
|
||||
notifPaneController.icon.setImage(successIcon)
|
||||
true
|
||||
}
|
||||
case NOTIFICATION_ERROR => {
|
||||
notifPaneController.rootPane.setStyle("-fx-border-color: #d43c4e")
|
||||
notifPaneController.icon.setImage(errorIcon)
|
||||
false
|
||||
}
|
||||
case NOTIFICATION_INFO => {
|
||||
notifPaneController.rootPane.setStyle("-fx-border-color: #409be6")
|
||||
notifPaneController.icon.setImage(infoIcon)
|
||||
true
|
||||
}
|
||||
case _ => true
|
||||
}
|
||||
|
||||
// in/out animations
|
||||
val showAnimation = getShowAnimation(notifPaneController.rootPane)
|
||||
|
||||
val dismissAnimation = getDismissAnimation(notifPaneController.rootPane)
|
||||
dismissAnimation.setOnFinished(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = notifsVBox.getChildren.remove(root)
|
||||
})
|
||||
notifPaneController.copyButton.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = {
|
||||
dismissAnimation.stop() // automatic dismiss is cancelled
|
||||
ContextMenuUtils.copyToClipboard(message)
|
||||
notifPaneController.copyButton.setOnAction(null)
|
||||
notifPaneController.copyButton.setText("Copied!")
|
||||
}
|
||||
})
|
||||
notifPaneController.closeButton.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent) = {
|
||||
dismissAnimation.stop()
|
||||
dismissAnimation.setDelay(Duration.ZERO)
|
||||
dismissAnimation.play()
|
||||
}
|
||||
})
|
||||
showAnimation.play()
|
||||
if (autoDismissed) {
|
||||
dismissAnimation.setDelay(Duration.seconds(12))
|
||||
dismissAnimation.play()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private def getDismissAnimation(element: Parent): Transition = {
|
||||
val fadeOutTransition = new FadeTransition(Duration.millis(200))
|
||||
fadeOutTransition.setFromValue(1)
|
||||
fadeOutTransition.setToValue(0)
|
||||
val translateRevTransition = new TranslateTransition(Duration.millis(450))
|
||||
translateRevTransition.setFromX(0)
|
||||
translateRevTransition.setToX(150)
|
||||
val scaleTransition = new ScaleTransition(Duration.millis(350))
|
||||
scaleTransition.setFromY(1)
|
||||
scaleTransition.setToY(0)
|
||||
new ParallelTransition(element, fadeOutTransition, translateRevTransition, scaleTransition)
|
||||
}
|
||||
|
||||
private def getShowAnimation(element: Parent): Transition = {
|
||||
val fadeTransition = new FadeTransition(Duration.millis(400))
|
||||
fadeTransition.setFromValue(0)
|
||||
fadeTransition.setToValue(.95)
|
||||
val translateTransition = new TranslateTransition(Duration.millis(500))
|
||||
translateTransition.setFromX(150)
|
||||
translateTransition.setToX(0)
|
||||
new ParallelTransition(element, fadeTransition, translateTransition)
|
||||
}
|
||||
}
|
@ -1,144 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import com.google.common.base.Strings
|
||||
import fr.acinq.bitcoin.SatoshiLong
|
||||
import fr.acinq.eclair.blockchain.fee.{FeeratePerByte, FeeratePerKw}
|
||||
import fr.acinq.eclair.channel.ChannelFlags
|
||||
import fr.acinq.eclair.gui.utils.Constants
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.io.{NodeURI, Peer}
|
||||
import fr.acinq.eclair.{CoinUtils, MilliSatoshi, _}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
import javafx.stage.Stage
|
||||
|
||||
import java.lang.Boolean
|
||||
import scala.concurrent.ExecutionContext
|
||||
import scala.concurrent.duration._
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* Created by DPA on 23/09/2016.
|
||||
*/
|
||||
class OpenChannelController(val handlers: Handlers, val stage: Stage) extends Logging {
|
||||
|
||||
@FXML var host: TextField = _
|
||||
@FXML var hostError: Label = _
|
||||
@FXML var simpleConnection: CheckBox = _
|
||||
@FXML var fundingSat: TextField = _
|
||||
@FXML var fundingSatError: Label = _
|
||||
@FXML var fundingUnit: ComboBox[String] = _
|
||||
@FXML var pushMsatField: TextField = _
|
||||
@FXML var pushMsatError: Label = _
|
||||
@FXML var feerateField: TextField = _
|
||||
@FXML var feerateError: Label = _
|
||||
@FXML var publicChannel: CheckBox = _
|
||||
|
||||
@FXML def initialize() = {
|
||||
fundingUnit.setItems(Constants.FX_UNITS_ARRAY_NO_MSAT)
|
||||
fundingUnit.setValue(FxApp.getUnit.label)
|
||||
|
||||
handlers.getFundingFeeRatePerKb().onComplete {
|
||||
case Success(feeSatKb) =>
|
||||
feerateField.setText(FeeratePerByte(feeSatKb.feerate / 1000).toString)
|
||||
feerateError.setText("")
|
||||
case Failure(t) =>
|
||||
logger.error(s"error when estimating funding fee from GUI: ${t.getLocalizedMessage}")
|
||||
feerateField.setText("")
|
||||
feerateError.setText("Could not estimate fees.")
|
||||
} (ExecutionContext.Implicits.global)
|
||||
|
||||
simpleConnection.selectedProperty.addListener(new ChangeListener[Boolean] {
|
||||
override def changed(observable: ObservableValue[_ <: Boolean], oldValue: Boolean, newValue: Boolean) = {
|
||||
fundingSat.setDisable(newValue)
|
||||
pushMsatField.setDisable(newValue)
|
||||
fundingUnit.setDisable(newValue)
|
||||
}
|
||||
})
|
||||
|
||||
host.textProperty.addListener(new ChangeListener[String] {
|
||||
def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String): Unit = Try(NodeURI.parse(newValue)) match {
|
||||
case Failure(t) => hostError.setText(t.getLocalizedMessage)
|
||||
case _ => hostError.setText("")
|
||||
}
|
||||
})
|
||||
|
||||
fundingSat.textProperty.addListener(new ChangeListener[String] {
|
||||
def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String): Unit = {
|
||||
Try(CoinUtils.convertStringAmountToSat(newValue, fundingUnit.getValue)) match {
|
||||
case Success(capacitySat) if capacitySat <= 0.sat =>
|
||||
fundingSatError.setText("Capacity must be greater than 0")
|
||||
case Success(capacitySat) if capacitySat < 50000.sat =>
|
||||
fundingSatError.setText("Capacity is low and the channel may not be able to open")
|
||||
case Success(_) => fundingSatError.setText("")
|
||||
case _ => fundingSatError.setText("Capacity is not valid")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@FXML def handleOpen(event: ActionEvent) = {
|
||||
clearErrors()
|
||||
Try(NodeURI.parse(host.getText)) match {
|
||||
case Success(nodeUri) if simpleConnection.isSelected =>
|
||||
handlers.open(nodeUri, None)
|
||||
stage.close()
|
||||
case Success(nodeUri) =>
|
||||
(Try(CoinUtils.convertStringAmountToSat(fundingSat.getText, fundingUnit.getValue)),
|
||||
Try(if (Strings.isNullOrEmpty(pushMsatField.getText())) 0L else pushMsatField.getText().toLong),
|
||||
Try(if (Strings.isNullOrEmpty(feerateField.getText())) None else Some(feerateField.getText().toLong))) match {
|
||||
case (Success(capacitySat), _, _) if capacitySat <= 0.sat =>
|
||||
fundingSatError.setText("Capacity must be greater than 0")
|
||||
case (Success(capacitySat), Success(pushMsat), _) if pushMsat > capacitySat.toMilliSatoshi.toLong =>
|
||||
pushMsatError.setText("Push must be less or equal to capacity")
|
||||
case (Success(_), Success(pushMsat), _) if pushMsat < 0 =>
|
||||
pushMsatError.setText("Push must be positive")
|
||||
case (Success(_), Success(_), Success(Some(feerate))) if feerate <= 0 =>
|
||||
feerateError.setText("Fee rate must be greater than 0")
|
||||
case (Success(capacitySat), Success(pushMsat), Success(feeratePerByte_opt)) =>
|
||||
val channelFlags = if (publicChannel.isSelected) ChannelFlags.AnnounceChannel else ChannelFlags.Empty
|
||||
handlers.open(nodeUri, Some(Peer.OpenChannel(nodeUri.nodeId, capacitySat, MilliSatoshi(pushMsat), None, feeratePerByte_opt.map(f => FeeratePerKw(FeeratePerByte(f.sat))), Some(channelFlags), Some(30 seconds))))
|
||||
stage.close()
|
||||
case (Failure(t), _, _) =>
|
||||
logger.error(s"could not parse capacity with cause=${t.getLocalizedMessage}")
|
||||
fundingSatError.setText("Capacity is not valid")
|
||||
case (_, Failure(t), _) =>
|
||||
logger.error(s"could not parse push amount with cause=${t.getLocalizedMessage}")
|
||||
pushMsatError.setText("Push amount is not valid")
|
||||
case (_, _, Failure(t)) =>
|
||||
logger.error(s"could not parse fee rate with cause=${t.getLocalizedMessage}")
|
||||
feerateError.setText("Fee rate is not valid")
|
||||
}
|
||||
case Failure(t) => logger.error(s"could not parse node uri with cause=${t.getLocalizedMessage}")
|
||||
hostError.setText(t.getLocalizedMessage)
|
||||
}
|
||||
}
|
||||
|
||||
private def clearErrors(): Unit = {
|
||||
hostError.setText("")
|
||||
fundingSatError.setText("")
|
||||
pushMsatError.setText("")
|
||||
feerateError.setText("")
|
||||
}
|
||||
|
||||
@FXML def handleClose(event: ActionEvent) = stage.close()
|
||||
}
|
@ -1,143 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import fr.acinq.eclair.gui.utils._
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.{CoinUtils, MilliSatoshi, MilliSatoshiLong}
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.application.Platform
|
||||
import javafx.event.ActionEvent
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control._
|
||||
import javafx.scene.image.{ImageView, WritableImage}
|
||||
import javafx.scene.layout.GridPane
|
||||
import javafx.stage.Stage
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* Created by DPA on 23/09/2016.
|
||||
*/
|
||||
class ReceivePaymentController(val handlers: Handlers, val stage: Stage) extends Logging {
|
||||
|
||||
@FXML var amount: TextField = _
|
||||
@FXML var amountError: Label = _
|
||||
@FXML var unit: ComboBox[String] = _
|
||||
@FXML var description: TextArea = _
|
||||
@FXML var prependPrefixCheckbox: CheckBox = _
|
||||
|
||||
@FXML var resultBox: GridPane = _
|
||||
// the content of this field is generated and readonly
|
||||
@FXML var paymentRequestTextArea: TextArea = _
|
||||
@FXML var paymentRequestQRCode: ImageView = _
|
||||
|
||||
@FXML def initialize = {
|
||||
unit.setItems(Constants.FX_UNITS_ARRAY)
|
||||
unit.setValue(FxApp.getUnit.label)
|
||||
resultBox.managedProperty().bind(resultBox.visibleProperty())
|
||||
stage.sizeToScene()
|
||||
}
|
||||
|
||||
@FXML def handleCopyInvoice(event: ActionEvent) = ContextMenuUtils.copyToClipboard(paymentRequestTextArea.getText)
|
||||
|
||||
/**
|
||||
* Generates a payment request from the amount/unit set in form. Displays an error if the generation fails.
|
||||
* Amount field content must obviously be numeric. It is also validated against minimal/maximal HTLC values.
|
||||
*
|
||||
* @param event
|
||||
*/
|
||||
@FXML def handleGenerate(event: ActionEvent) = {
|
||||
clearError()
|
||||
amount.getText match {
|
||||
case "" => createPaymentRequest(None)
|
||||
case GUIValidators.amountDecRegex(_*) =>
|
||||
Try(CoinUtils.convertStringAmountToMsat(amount.getText, unit.getValue)) match {
|
||||
case Success(amountMsat) if amountMsat < 0.msat =>
|
||||
handleError("Amount must be greater than 0")
|
||||
case Failure(_) =>
|
||||
handleError("Amount is incorrect")
|
||||
case Success(amountMsat) => createPaymentRequest(Some(amountMsat))
|
||||
}
|
||||
case _ => handleError("Amount must be a number")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Display error message
|
||||
*
|
||||
* @param message
|
||||
*/
|
||||
private def handleError(message: String): Unit = {
|
||||
paymentRequestTextArea.setText("")
|
||||
amountError.setText(message)
|
||||
amountError.setOpacity(1)
|
||||
}
|
||||
|
||||
private def clearError(): Unit = {
|
||||
paymentRequestTextArea.setText("")
|
||||
amountError.setText("")
|
||||
amountError.setOpacity(0)
|
||||
}
|
||||
|
||||
/**
|
||||
* Ask eclair-core to create a Payment Request. If successful a QR code is generated and displayed, otherwise
|
||||
* an error message is shown.
|
||||
*
|
||||
* @param amount_opt optional amount of the payment request, in millisatoshi
|
||||
*/
|
||||
private def createPaymentRequest(amount_opt: Option[MilliSatoshi]) = {
|
||||
logger.debug(s"generate payment request for amount_opt=${amount_opt.getOrElse("N/A")} description=${description.getText()}")
|
||||
handlers.receive(amount_opt, description.getText) onComplete {
|
||||
case Success(s) =>
|
||||
val pr = if (prependPrefixCheckbox.isSelected) s"lightning:$s" else s
|
||||
Try(QRCodeUtils.createQRCode(pr.toUpperCase, margin = -1)) match {
|
||||
case Success(wImage) => displayPaymentRequestQR(pr, Some(wImage))
|
||||
case Failure(t) => displayPaymentRequestQR(pr, None)
|
||||
}
|
||||
case Failure(t) =>
|
||||
logger.error("Could not generate payment request", t)
|
||||
Platform.runLater(new Runnable {
|
||||
def run = GUIValidators.validate(amountError, "The payment request could not be generated", false)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a QR Code from a QR code image.
|
||||
*
|
||||
* @param pr payment request described by the QR code
|
||||
* @param image QR code source image
|
||||
*/
|
||||
private def displayPaymentRequestQR(pr: String, image: Option[WritableImage]) = Platform.runLater(new Runnable {
|
||||
def run = {
|
||||
paymentRequestTextArea.setText(pr)
|
||||
if ("".equals(pr)) {
|
||||
resultBox.setVisible(false)
|
||||
resultBox.setMaxHeight(0)
|
||||
} else {
|
||||
resultBox.setVisible(true)
|
||||
resultBox.setMaxHeight(Double.MaxValue)
|
||||
}
|
||||
image.map(paymentRequestQRCode.setImage(_))
|
||||
stage.sizeToScene()
|
||||
}
|
||||
})
|
||||
|
||||
@FXML def handleClose(event: ActionEvent) = stage.close
|
||||
}
|
@ -1,130 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import fr.acinq.eclair.CoinUtils
|
||||
import fr.acinq.eclair.gui.{FxApp, Handlers}
|
||||
import fr.acinq.eclair.payment.PaymentRequest
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.beans.value.{ChangeListener, ObservableValue}
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{Button, Label, TextArea, TextField}
|
||||
import javafx.scene.input.KeyCode.{ENTER, TAB}
|
||||
import javafx.scene.input.KeyEvent
|
||||
import javafx.stage.Stage
|
||||
|
||||
import scala.util.{Failure, Success, Try}
|
||||
|
||||
/**
|
||||
* Created by DPA on 23/09/2016.
|
||||
*/
|
||||
class SendPaymentController(val handlers: Handlers, val stage: Stage) extends Logging {
|
||||
|
||||
@FXML var paymentRequest: TextArea = _
|
||||
@FXML var paymentRequestError: Label = _
|
||||
@FXML var nodeIdField: TextField = _
|
||||
@FXML var descriptionLabel: Label = _
|
||||
@FXML var descriptionField: TextArea = _
|
||||
@FXML var amountFieldLabel: Label = _
|
||||
@FXML var amountField: TextField = _
|
||||
@FXML var amountFieldError: Label = _
|
||||
@FXML var paymentHashField: TextField = _
|
||||
@FXML var sendButton: Button = _
|
||||
|
||||
@FXML def initialize(): Unit = {
|
||||
|
||||
// set the user preferred unit
|
||||
amountFieldLabel.setText(s"Amount (${FxApp.getUnit.shortLabel})")
|
||||
|
||||
// ENTER or TAB events in the paymentRequest textarea instead fire or focus sendButton
|
||||
paymentRequest.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler[KeyEvent] {
|
||||
def handle(event: KeyEvent) = {
|
||||
event.getCode match {
|
||||
case ENTER =>
|
||||
sendButton.fire()
|
||||
event.consume()
|
||||
case TAB =>
|
||||
sendButton.requestFocus()
|
||||
event.consume()
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
})
|
||||
paymentRequest.textProperty.addListener(new ChangeListener[String] {
|
||||
def changed(observable: ObservableValue[_ <: String], oldValue: String, newValue: String): Unit = {
|
||||
readPaymentRequest() match {
|
||||
case Success(pr) => setUIFields(pr)
|
||||
case Failure(f) =>
|
||||
paymentRequestError.setText("Could not read this payment request")
|
||||
descriptionLabel.setText("")
|
||||
nodeIdField.setText("")
|
||||
paymentHashField.setText("")
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to read the payment request string in the payment request input field
|
||||
*
|
||||
* @return a Try containing the payment request object, if successfully parsed
|
||||
*/
|
||||
private def readPaymentRequest(): Try[PaymentRequest] = {
|
||||
clearErrors()
|
||||
val prString: String = paymentRequest.getText.trim match {
|
||||
case s if s.startsWith("lightning://") => s.replaceAll("lightning://", "")
|
||||
case s if s.startsWith("lightning:") => s.replaceAll("lightning:", "")
|
||||
case s => s
|
||||
}
|
||||
Try(PaymentRequest.read(prString))
|
||||
}
|
||||
|
||||
private def setUIFields(pr: PaymentRequest) = {
|
||||
pr.amount.foreach(amount => amountField.setText(CoinUtils.rawAmountInUnit(amount, FxApp.getUnit).bigDecimal.toPlainString))
|
||||
pr.description match {
|
||||
case Left(s) => descriptionField.setText(s)
|
||||
case Right(hash) =>
|
||||
descriptionLabel.setText("Description's Hash")
|
||||
descriptionField.setText(hash.toString())
|
||||
}
|
||||
nodeIdField.setText(pr.nodeId.toString)
|
||||
paymentHashField.setText(pr.paymentHash.toString)
|
||||
}
|
||||
|
||||
@FXML def handleSend(event: ActionEvent): Unit = {
|
||||
(Try(CoinUtils.convertStringAmountToMsat(amountField.getText(), FxApp.getUnit.code)), readPaymentRequest()) match {
|
||||
case (Success(amountMsat), Success(pr)) =>
|
||||
// we always override the payment request amount with the one from the UI
|
||||
Try(handlers.send(Some(amountMsat.toLong), pr)) match {
|
||||
case Success(_) => stage.close()
|
||||
case Failure(f) => paymentRequestError.setText(s"Invalid Payment Request: ${f.getMessage}")
|
||||
}
|
||||
case (_, Success(_)) => amountFieldError.setText("Invalid amount")
|
||||
case (_, Failure(_)) => paymentRequestError.setText("Could not read this payment request")
|
||||
}
|
||||
}
|
||||
|
||||
@FXML def handleClose(event: ActionEvent): Unit = {
|
||||
stage.close()
|
||||
}
|
||||
|
||||
private def clearErrors(): Unit = {
|
||||
paymentRequestError.setText("")
|
||||
amountFieldError.setText("")
|
||||
}
|
||||
}
|
@ -1,86 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.controllers
|
||||
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.animation._
|
||||
import javafx.application.HostServices
|
||||
import javafx.fxml.FXML
|
||||
import javafx.scene.control.{Button, Label}
|
||||
import javafx.scene.image.ImageView
|
||||
import javafx.scene.layout.{Pane, VBox}
|
||||
import javafx.util.Duration
|
||||
|
||||
/**
|
||||
* Created by DPA on 22/09/2016.
|
||||
*/
|
||||
class SplashController(hostServices: HostServices) extends Logging {
|
||||
|
||||
@FXML var splash: Pane = _
|
||||
@FXML var img: ImageView = _
|
||||
@FXML var imgBlurred: ImageView = _
|
||||
@FXML var closeButton: Button = _
|
||||
@FXML var errorBox: VBox = _
|
||||
@FXML var logBox: VBox = _
|
||||
|
||||
/**
|
||||
* Start an animation when the splash window is initialized
|
||||
*/
|
||||
@FXML def initialize = {
|
||||
val timeline = new Timeline(
|
||||
new KeyFrame(Duration.ZERO,
|
||||
new KeyValue(img.opacityProperty, double2Double(0), Interpolator.EASE_IN),
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(1.0), Interpolator.EASE_IN)),
|
||||
new KeyFrame(Duration.millis(1000.0d),
|
||||
new KeyValue(img.opacityProperty, double2Double(1.0), Interpolator.EASE_OUT),
|
||||
new KeyValue(imgBlurred.opacityProperty, double2Double(0), Interpolator.EASE_OUT)))
|
||||
timeline.play()
|
||||
}
|
||||
|
||||
@FXML def closeAndKill = System.exit(0)
|
||||
|
||||
@FXML def openGithubPage = hostServices.showDocument("https://github.com/ACINQ/eclair/blob/master/README.md")
|
||||
|
||||
def addLog(message: String) = {
|
||||
val l = new Label
|
||||
l.setText(message)
|
||||
l.setWrapText(true)
|
||||
logBox.getChildren.add(l)
|
||||
}
|
||||
|
||||
def addError(message: String) = {
|
||||
val l = new Label
|
||||
l.setText(message)
|
||||
l.setWrapText(true)
|
||||
l.getStyleClass.add("text-error")
|
||||
logBox.getChildren.add(l)
|
||||
}
|
||||
|
||||
/**
|
||||
* Shows the error Box with a fade+translate transition.
|
||||
*/
|
||||
def showErrorBox = {
|
||||
val fadeTransition = new FadeTransition(Duration.millis(400))
|
||||
fadeTransition.setFromValue(0)
|
||||
fadeTransition.setToValue(1)
|
||||
val translateTransition = new TranslateTransition(Duration.millis(500))
|
||||
translateTransition.setFromY(20)
|
||||
translateTransition.setToY(0)
|
||||
val t = new ParallelTransition(errorBox, fadeTransition, translateTransition)
|
||||
t.play
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.stages
|
||||
|
||||
import fr.acinq.eclair.gui.controllers.AboutController
|
||||
import javafx.application.HostServices
|
||||
import javafx.event.EventHandler
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.input.KeyCode._
|
||||
import javafx.scene.input.KeyEvent
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Modality, Stage, StageStyle}
|
||||
|
||||
/**
|
||||
* Created by DPA on 28/09/2016.
|
||||
*/
|
||||
class AboutStage(hostServices: HostServices) extends Stage() {
|
||||
initModality(Modality.WINDOW_MODAL)
|
||||
initStyle(StageStyle.DECORATED)
|
||||
getIcons().add(new Image("/gui/commons/images/eclair-square.png", false))
|
||||
setTitle("About Eclair")
|
||||
setResizable(false)
|
||||
setWidth(500)
|
||||
setHeight(200)
|
||||
|
||||
// get fxml/controller
|
||||
val openFXML = new FXMLLoader(getClass.getResource("/gui/modals/about.fxml"))
|
||||
openFXML.setController(new AboutController(hostServices))
|
||||
val root = openFXML.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
|
||||
val self = this
|
||||
scene.addEventFilter(KeyEvent.KEY_PRESSED, new EventHandler[KeyEvent]() {
|
||||
def handle(event: KeyEvent) = {
|
||||
event.getCode match {
|
||||
case ESCAPE =>
|
||||
self.close
|
||||
case _ =>
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
setScene(scene)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.stages
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.controllers.NodeInfoController
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Modality, Stage, StageStyle}
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class NodeInfoStage(address: String, handlers: Handlers) extends Stage() {
|
||||
initModality(Modality.WINDOW_MODAL)
|
||||
initStyle(StageStyle.DECORATED)
|
||||
getIcons.add(new Image("/gui/commons/images/eclair-square.png", false))
|
||||
setTitle("Node information")
|
||||
setMinWidth(590)
|
||||
setWidth(590)
|
||||
setMinHeight(200)
|
||||
setHeight(200)
|
||||
setResizable(false)
|
||||
|
||||
// get fxml/controller
|
||||
val nodeInfo = new FXMLLoader(getClass.getResource("/gui/modals/nodeInfo.fxml"))
|
||||
nodeInfo.setController(new NodeInfoController(address, handlers, this))
|
||||
val root = nodeInfo.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
setScene(scene)
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.stages
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.controllers.OpenChannelController
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Modality, Stage, StageStyle}
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class OpenChannelStage(handlers: Handlers) extends Stage() {
|
||||
initModality(Modality.WINDOW_MODAL)
|
||||
initStyle(StageStyle.DECORATED)
|
||||
getIcons.add(new Image("/gui/commons/images/eclair-square.png", false))
|
||||
setTitle("Open a new channel")
|
||||
setMinWidth(550)
|
||||
setWidth(550)
|
||||
setMinHeight(400)
|
||||
setHeight(400)
|
||||
|
||||
// get fxml/controller
|
||||
val openFXML = new FXMLLoader(getClass.getResource("/gui/modals/openChannel.fxml"))
|
||||
openFXML.setController(new OpenChannelController(handlers, this))
|
||||
val root = openFXML.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
setScene(scene)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.stages
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.controllers.ReceivePaymentController
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Modality, Stage, StageStyle}
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class ReceivePaymentStage(handlers: Handlers) extends Stage() {
|
||||
initModality(Modality.WINDOW_MODAL)
|
||||
initStyle(StageStyle.DECORATED)
|
||||
getIcons().add(new Image("/gui/commons/images/eclair-square.png", false))
|
||||
setTitle("Receive a Payment")
|
||||
setMinWidth(590)
|
||||
setWidth(590)
|
||||
setMinHeight(200)
|
||||
setHeight(200)
|
||||
setResizable(false)
|
||||
|
||||
// get fxml/controller
|
||||
val receivePayment = new FXMLLoader(getClass.getResource("/gui/modals/receivePayment.fxml"))
|
||||
receivePayment.setController(new ReceivePaymentController(handlers, this))
|
||||
val root = receivePayment.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
setScene(scene)
|
||||
}
|
@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.stages
|
||||
|
||||
import fr.acinq.eclair.gui.Handlers
|
||||
import fr.acinq.eclair.gui.controllers.SendPaymentController
|
||||
import grizzled.slf4j.Logging
|
||||
import javafx.fxml.FXMLLoader
|
||||
import javafx.scene.image.Image
|
||||
import javafx.scene.{Parent, Scene}
|
||||
import javafx.stage.{Modality, Stage, StageStyle}
|
||||
|
||||
/**
|
||||
* Created by PM on 16/08/2016.
|
||||
*/
|
||||
class SendPaymentStage(handlers: Handlers) extends Stage() with Logging {
|
||||
initModality(Modality.WINDOW_MODAL)
|
||||
initStyle(StageStyle.DECORATED)
|
||||
getIcons().add(new Image("/gui/commons/images/eclair-square.png", false))
|
||||
setTitle("Send a Payment Request")
|
||||
setMinWidth(450)
|
||||
setWidth(450)
|
||||
setMinHeight(550)
|
||||
setHeight(550)
|
||||
|
||||
// get fxml/controller
|
||||
val receivePayment = new FXMLLoader(getClass.getResource("/gui/modals/sendPayment.fxml"))
|
||||
receivePayment.setController(new SendPaymentController(handlers, this))
|
||||
val root = receivePayment.load[Parent]
|
||||
|
||||
// create scene
|
||||
val scene = new Scene(root)
|
||||
setScene(scene)
|
||||
}
|
@ -1,25 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import fr.acinq.eclair._
|
||||
import javafx.collections.FXCollections
|
||||
|
||||
object Constants {
|
||||
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)
|
||||
}
|
@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import javafx.event.{ActionEvent, EventHandler}
|
||||
import javafx.scene.control.{ContextMenu, MenuItem}
|
||||
import javafx.scene.input.{Clipboard, ClipboardContent}
|
||||
|
||||
import scala.collection.immutable.List
|
||||
|
||||
/**
|
||||
* Created by DPA on 28/09/2016.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Action to copy a value
|
||||
*
|
||||
* @param label label of the copy action in the context menu, defaults to copy value
|
||||
* @param value the value to copy
|
||||
*/
|
||||
case class CopyAction(label: String = "Copy Value", value: String)
|
||||
|
||||
object ContextMenuUtils {
|
||||
val clip = Clipboard.getSystemClipboard
|
||||
|
||||
/**
|
||||
* Builds a Context Menu containing a list of copy actions.
|
||||
*
|
||||
* @param actions list of copy action (label + value)
|
||||
* @return javafx context menu
|
||||
*/
|
||||
def buildCopyContext(actions: List[CopyAction]): ContextMenu = {
|
||||
val context = new ContextMenu()
|
||||
for (action <- actions) {
|
||||
context.getItems.addAll(buildCopyMenuItem(action))
|
||||
}
|
||||
context
|
||||
}
|
||||
|
||||
def buildCopyMenuItem(action: CopyAction): MenuItem = {
|
||||
val copyItem = new MenuItem(action.label)
|
||||
copyItem.setOnAction(new EventHandler[ActionEvent] {
|
||||
override def handle(event: ActionEvent): Unit = copyToClipboard(action.value)
|
||||
})
|
||||
copyItem
|
||||
}
|
||||
|
||||
def copyToClipboard(value: String) = {
|
||||
val clipContent = new ClipboardContent
|
||||
clipContent.putString(value)
|
||||
clip.setContent(clipContent)
|
||||
}
|
||||
}
|
@ -1,58 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import javafx.scene.control.Label
|
||||
|
||||
import scala.util.matching.Regex
|
||||
|
||||
/**
|
||||
* Created by DPA on 27/09/2016.
|
||||
*/
|
||||
object GUIValidators {
|
||||
val amountDecRegex = """(\d+)|(\d*\.[\d]{1,})""".r
|
||||
|
||||
/**
|
||||
* Validate a field against a regex. If field does not match the regex, validatorLabel is shown.
|
||||
*
|
||||
* @param field String content of the field to validate
|
||||
* @param validatorLabel JFX label associated to the field.
|
||||
* @param validatorMessage Message displayed if the field is invalid. It should describe the cause of
|
||||
* the validation failure
|
||||
* @param regex Scala regex that the field must match
|
||||
* @return true if field is valid, false otherwise
|
||||
*/
|
||||
def validate(field: String, validatorLabel: Label, validatorMessage: String, regex: Regex): Boolean = {
|
||||
return field match {
|
||||
case regex(_*) => validate(validatorLabel, validatorMessage, true)
|
||||
case _ => validate(validatorLabel, validatorMessage, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Displays a label with an error message.
|
||||
*
|
||||
* @param errorLabel JFX label containing an error message
|
||||
* @param validCondition if true the label is hidden, else it is shown
|
||||
* @return true if field is valid, false otherwise
|
||||
*/
|
||||
def validate(errorLabel: Label, errorMessage: String, validCondition: Boolean): Boolean = {
|
||||
errorLabel.setOpacity(if (validCondition) 0 else 1)
|
||||
errorLabel.setText(errorMessage)
|
||||
validCondition
|
||||
}
|
||||
}
|
@ -1,65 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import java.util
|
||||
|
||||
import javafx.collections.FXCollections
|
||||
|
||||
class IndexedObservableList[K, V] {
|
||||
|
||||
val map2index = new util.HashMap[K, Int]()
|
||||
val list = FXCollections.observableArrayList[V]()
|
||||
|
||||
def containsKey(key: K) = {
|
||||
map2index.containsKey(key)
|
||||
}
|
||||
|
||||
def get(key: K): V = {
|
||||
if (map2index.containsKey(key)) {
|
||||
val index = map2index.get(key)
|
||||
list.get(index)
|
||||
} else throw new RuntimeException("key not found")
|
||||
}
|
||||
|
||||
def put(key: K, value: V) = {
|
||||
if (map2index.containsKey(key)) {
|
||||
val index = map2index.get(key)
|
||||
list.set(index, value)
|
||||
} else {
|
||||
val index = list.size()
|
||||
map2index.put(key, index)
|
||||
list.add(index, value)
|
||||
}
|
||||
}
|
||||
|
||||
def remove(key: K) = {
|
||||
if (map2index.containsKey(key)) {
|
||||
val index = map2index.get(key)
|
||||
map2index.remove(key)
|
||||
list.remove(index)
|
||||
// now we need to decrement all higher indices by 1
|
||||
import scala.jdk.CollectionConverters._
|
||||
for (entry <- map2index.entrySet().asScala) {
|
||||
if (entry.getValue > index) {
|
||||
map2index.put(entry.getKey, entry.getValue - 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019 ACINQ SAS
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package fr.acinq.eclair.gui.utils
|
||||
|
||||
import com.google.zxing.qrcode.QRCodeWriter
|
||||
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel
|
||||
import com.google.zxing.{BarcodeFormat, EncodeHintType}
|
||||
import javafx.scene.image.WritableImage
|
||||
import javafx.scene.paint.Color
|
||||
|
||||
/**
|
||||
* Created by DPA on 22/12/2017.
|
||||
*/
|
||||
case object QRCodeUtils {
|
||||
|
||||
def createQRCode(data: String, width: Int = 250, height: Int = 250, margin: Int = 5): WritableImage = {
|
||||
import scala.jdk.CollectionConverters._
|
||||
val hintMap = collection.mutable.Map[EncodeHintType, Object]()
|
||||
hintMap.put(EncodeHintType.CHARACTER_SET, "UTF-8")
|
||||
hintMap.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.L)
|
||||
hintMap.put(EncodeHintType.MARGIN, margin.toString)
|
||||
val qrWriter = new QRCodeWriter
|
||||
val byteMatrix = qrWriter.encode(data, BarcodeFormat.QR_CODE, width, height, hintMap.asJava)
|
||||
val writableImage = new WritableImage(width, height)
|
||||
val pixelWriter = writableImage.getPixelWriter
|
||||
for (i <- 0 until byteMatrix.getWidth) {
|
||||
for (j <- 0 until byteMatrix.getWidth) {
|
||||
if (byteMatrix.get(i, j)) {
|
||||
pixelWriter.setColor(i, j, Color.BLACK)
|
||||
} else {
|
||||
pixelWriter.setColor(i, j, Color.WHITE)
|
||||
}
|
||||
}
|
||||
}
|
||||
writableImage
|
||||
}
|
||||
}
|
@ -1,31 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
~ Copyright 2019 ACINQ SAS
|
||||
~
|
||||
~ Licensed under the Apache License, Version 2.0 (the "License");
|
||||
~ you may not use this file except in compliance with the License.
|
||||
~ You may obtain a copy of the License at
|
||||
~
|
||||
~ http://www.apache.org/licenses/LICENSE-2.0
|
||||
~
|
||||
~ Unless required by applicable law or agreed to in writing, software
|
||||
~ distributed under the License is distributed on an "AS IS" BASIS,
|
||||
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
~ See the License for the specific language governing permissions and
|
||||
~ limitations under the License.
|
||||
-->
|
||||
|
||||
<configuration scan="true" debug="false">
|
||||
|
||||
<appender name="CONSOLE" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<target>System.out</target>
|
||||
<encoder>
|
||||
<pattern>%date{HH:mm:ss.SSS} %highlight(%-5level) %X{akkaSource} - %msg%ex{12}%n</pattern>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<root level="DEBUG">
|
||||
<appender-ref ref="CONSOLE"/>
|
||||
</root>
|
||||
|
||||
</configuration>
|