1
0
mirror of https://github.com/ACINQ/eclair.git synced 2024-11-19 01:43:22 +01:00

Remove the GUI (#1981)

So long old friend, and thanks for all the demos!
This commit is contained in:
Bastien Teinturier 2021-10-01 14:46:27 +02:00 committed by GitHub
parent 97393b13b4
commit fd565040d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
64 changed files with 2 additions and 4924 deletions

View File

@ -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

View File

@ -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

View File

@ -8,4 +8,3 @@ comment:
ignore:
- "eclair-node/**/*"
- "eclair-node-gui/**/*"

View File

@ -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.

View File

@ -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>

View File

@ -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>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

View File

@ -1,5 +0,0 @@
eclair {
gui {
unit = btc
}
}

View File

@ -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

View File

@ -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 "$@"

View File

@ -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;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 318 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 524 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 526 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 513 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

View File

@ -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>

View File

@ -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;
}

View File

@ -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 &quot;Channels&quot; &gt; &quot;Open Channel...&quot;"
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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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;
}

View File

@ -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>

View File

@ -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)
}
}

View File

@ -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
}

View File

@ -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}")
}
}
}

View File

@ -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"))
}
}

View File

@ -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
}
}

View File

@ -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")
}

View File

@ -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}
|"""
}

View File

@ -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))
}
}

View File

@ -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()
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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()
}

View File

@ -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
}

View File

@ -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("")
}
}

View File

@ -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
}
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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
}
}

View File

@ -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>

View File

@ -27,7 +27,6 @@
<module>eclair-core</module>
<module>eclair-front</module>
<module>eclair-node</module>
<module>eclair-node-gui</module>
</modules>
<description>A scala implementation of the Lightning Network</description>