Add initial form for configuring new nodes.

This commit is contained in:
Chris Rankin 2017-01-26 09:55:45 +00:00
parent 1bef874e27
commit cc1d3e0ccf
10 changed files with 292 additions and 65 deletions

View File

@ -3,10 +3,12 @@ version '0.7-SNAPSHOT'
buildscript { buildscript {
ext.kotlin_version = '1.0.6' ext.kotlin_version = '1.0.6'
ext.tornadofx_version = '1.6.0'
ext.jna_version = '4.1.0' ext.jna_version = '4.1.0'
ext.purejavacomm_version = '0.0.17' ext.purejavacomm_version = '0.0.17'
ext.guava_version = '14.0.1' ext.guava_version = '14.0.1'
ext.slf4j_version = '1.7.22' ext.slf4j_version = '1.7.22'
ext.logback_version = '1.1.3'
repositories { repositories {
mavenCentral() mavenCentral()
@ -18,8 +20,11 @@ buildscript {
apply plugin: 'java' apply plugin: 'java'
apply plugin: 'kotlin' apply plugin: 'kotlin'
apply plugin: 'application'
sourceCompatibility = 1.8 sourceCompatibility = 1.8
mainClassName = 'net.corda.demobench.DemoBench'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.class=net.corda.demobench.config.LoggingConfig']
repositories { repositories {
flatDir { flatDir {
@ -34,6 +39,8 @@ repositories {
} }
dependencies { dependencies {
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
compile "no.tornado:tornadofx:$tornadofx_version"
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "net.java.dev.jna:jna:$jna_version" compile "net.java.dev.jna:jna:$jna_version"
@ -42,12 +49,15 @@ dependencies {
compile "com.sparetimelabs:purejavacomm:$purejavacomm_version" compile "com.sparetimelabs:purejavacomm:$purejavacomm_version"
compile "org.slf4j:log4j-over-slf4j:$slf4j_version" compile "org.slf4j:log4j-over-slf4j:$slf4j_version"
compile "org.slf4j:jul-to-slf4j:$slf4j_version" compile "org.slf4j:jul-to-slf4j:$slf4j_version"
compile "ch.qos.logback:logback-classic:1.1.3" compile "ch.qos.logback:logback-classic:$logback_version"
compile ":jediterm-terminal-2.5" compile ":jediterm-terminal-2.5"
compile ':pty4j-0.7.2' compile ':pty4j-0.7.2'
testCompile group: 'junit', name: 'junit', version: '4.11' testCompile group: 'junit', name: 'junit', version: '4.11'
}
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
compile 'no.tornado:tornadofx:1.6.0' jar {
manifest {
attributes 'Main-Class': mainClassName
}
} }

View File

@ -28,7 +28,9 @@ public class R3Pty implements AutoCloseable {
@Override @Override
public void close() { public void close() {
LOG.info("Closing terminal '{}'", name); LOG.info("Closing terminal '{}'", name);
terminal.close(); if (terminal.getTerminalStarter() != null) {
terminal.close();
}
} }
public String getName() { public String getName() {

View File

@ -0,0 +1,29 @@
package net.corda.demobench.model
class NodeConfig(name: String, p2pPort: Int, artemisPort: Int, webPort: Int) {
private var keyValue: String = toKey(name)
val key : String
get() { return keyValue }
private var nameValue: String = name
val name : String
get() { return nameValue }
private var p2pPortValue: Int = p2pPort
val p2pPort : Int
get() { return p2pPortValue }
private var artemisPortValue: Int = artemisPort
val artemisPort : Int
get() { return artemisPortValue }
private var webPortValue: Int = webPort
val webPort : Int
get() { return webPortValue }
private fun toKey(value: String): String {
return value.replace("\\s++", "").toLowerCase()
}
}

View File

@ -0,0 +1,31 @@
package net.corda.demobench.model
import tornadofx.Controller
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.atomic.AtomicInteger
class NodeController : Controller() {
private val FIRST_PORT = 10000
private val nodes = ConcurrentHashMap<String, NodeConfig>()
private val port = AtomicInteger(FIRST_PORT)
fun validate(nodeData: NodeData): NodeConfig? {
val config = NodeConfig(
nodeData.legalName.value,
nodeData.p2pPort.value,
nodeData.artemisPort.value,
nodeData.webPort.value
)
if (nodes.putIfAbsent(config.key, config) != null) {
return null
}
return config
}
val nextPort: Int
get() { return port.andIncrement }
}

View File

@ -0,0 +1,13 @@
package net.corda.demobench.model
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleStringProperty
class NodeData {
var legalName : SimpleStringProperty = SimpleStringProperty("")
var p2pPort: SimpleIntegerProperty = SimpleIntegerProperty(0)
val artemisPort: SimpleIntegerProperty = SimpleIntegerProperty(0)
val webPort: SimpleIntegerProperty = SimpleIntegerProperty(0)
}

View File

@ -0,0 +1,12 @@
package net.corda.demobench.model
import tornadofx.ItemViewModel
class NodeDataModel : ItemViewModel<NodeData>(NodeData()) {
val legalName = bind { item?.legalName }
val p2pPort = bind { item?.p2pPort }
val artemisPort = bind { item?.artemisPort }
val webPort = bind { item?.webPort }
}

View File

@ -1,17 +1,11 @@
package net.corda.demobench.views package net.corda.demobench.views
import com.jediterm.terminal.TerminalColor
import com.jediterm.terminal.TextStyle
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider
import java.util.* import java.util.*
import javafx.application.Platform import javafx.application.Platform
import javafx.embed.swing.SwingNode
import javafx.scene.Parent import javafx.scene.Parent
import javafx.scene.control.Button import javafx.scene.control.Button
import javafx.scene.control.Tab import javafx.scene.control.Tab
import javafx.scene.control.TabPane import javafx.scene.control.TabPane
import javax.swing.SwingUtilities
import net.corda.demobench.pty.R3Pty
import net.corda.demobench.ui.CloseableTab import net.corda.demobench.ui.CloseableTab
import tornadofx.* import tornadofx.*
@ -22,8 +16,6 @@ class DemoBenchView : View("Corda Demo Bench") {
val addNodeButton by fxid<Button>() val addNodeButton by fxid<Button>()
val nodeTabPane by fxid<TabPane>() val nodeTabPane by fxid<TabPane>()
val settingsProvider = TerminalSettingsProvider()
init { init {
importStylesheet("/net/corda/demobench/style.css") importStylesheet("/net/corda/demobench/style.css")
@ -35,7 +27,7 @@ class DemoBenchView : View("Corda Demo Bench") {
} }
addNodeButton.setOnAction { addNodeButton.setOnAction {
val nodeTab = createNode() val nodeTab = createNodeTab()
nodeTabPane.selectionModel.select(nodeTab) nodeTabPane.selectionModel.select(nodeTab)
} }
addNodeButton.fire() addNodeButton.fire()
@ -47,40 +39,12 @@ class DemoBenchView : View("Corda Demo Bench") {
} }
} }
fun createNode(): CloseableTab { fun createNodeTab(): CloseableTab {
val pty = R3Pty("Banksy", settingsProvider, java.awt.Dimension(160, 80)) val nodeTabView = find<NodeTabView>()
val nodeTabView = NodeTabView(pty.name) val nodeTab = nodeTabView.nodeTab
val nodeTab = CloseableTab(pty.name, nodeTabView.root)
// Ensure that we close the terminal along with the tab.
nodeTab.setOnCloseRequest {
pty.close()
}
val swingTerminal = SwingNode()
SwingUtilities.invokeLater({
swingTerminal.content = pty.terminal
swingTerminal.setOnMouseClicked {
swingTerminal.requestFocus()
}
})
nodeTabPane.tabs.add(nodeTab) nodeTabPane.tabs.add(nodeTab)
nodeTab.content.add(swingTerminal)
pty.run("/bin/bash", "--login")
return nodeTab return nodeTab
} }
class TerminalSettingsProvider : DefaultSettingsProvider() {
override fun getDefaultStyle(): TextStyle {
return TextStyle(TerminalColor.WHITE, TerminalColor.BLACK)
}
override fun emulateX11CopyPaste(): Boolean {
return true
}
}
} }

View File

@ -1,28 +1,122 @@
package net.corda.demobench.views package net.corda.demobench.views
import javafx.scene.control.Button import java.text.DecimalFormat
import javafx.scene.control.Label import javafx.util.converter.NumberStringConverter
import javafx.scene.layout.Priority import net.corda.demobench.model.NodeController
import javafx.scene.layout.VBox import net.corda.demobench.model.NodeDataModel
import net.corda.demobench.ui.PropertyLabel import net.corda.demobench.ui.CloseableTab
import tornadofx.Fragment import tornadofx.*
import tornadofx.vgrow
class NodeTabView(name: String) : Fragment() { class NodeTabView : Fragment() {
override val root by fxml<VBox>() override val root = stackpane {}
val nodeName by fxid<Label>() private val INTEGER_FORMAT = DecimalFormat()
val p2pPort by fxid<PropertyLabel>() private val NOT_NUMBER = Regex("[^\\d]")
val states by fxid<PropertyLabel>()
val transactions by fxid<PropertyLabel>()
val balance by fxid<PropertyLabel>()
val viewDatabaseButton by fxid<Button>() private val model = NodeDataModel()
val launchExplorerButton by fxid<Button>() private val controller by inject<NodeController>()
private val nodeTerminalView = find<NodeTerminalView>()
private val nodeConfigView = pane {
form {
fieldset("Configuration") {
field("Node Name") {
textfield(model.legalName) {
minWidth = 200.0
maxWidth = 200.0
validator {
if (it.isNullOrBlank()) {
error("Node name is required")
} else {
null
}
}
}
}
field("P2P Port") {
textfield(model.p2pPort, NumberStringConverter(INTEGER_FORMAT)) {
minWidth = 100.0
maxWidth = 100.0
validator {
if ((it == null) || it.isEmpty()) {
error("Port number required")
} else if (it.contains(NOT_NUMBER)) {
error("Invalid port number")
} else {
null
}
}
}
}
field("Artemis Port") {
textfield(model.artemisPort, NumberStringConverter(INTEGER_FORMAT)) {
minWidth = 100.0
maxWidth = 100.0
validator {
if ((it == null) || it.isEmpty()) {
error("Port number required")
} else if (it.contains(NOT_NUMBER)) {
error("Invalid port number")
} else {
null
}
}
}
}
field("Web Port") {
textfield(model.webPort, NumberStringConverter(INTEGER_FORMAT)) {
minWidth = 100.0
maxWidth = 100.0
validator {
if ((it == null) || it.isEmpty()) {
error("Port number required")
} else if (it.contains(NOT_NUMBER)) {
error("Invalid port number")
} else {
null
}
}
}
}
}
fieldset("Plugins") {
}
button("Create Node") {
setOnAction() {
launch()
}
}
}
}
val nodeTab = CloseableTab("New Node", root)
fun launch() {
model.commit()
val config = controller.validate(model.item)
if (config != null) {
nodeConfigView.isVisible = false
nodeTab.text = config.name
nodeTerminalView.open(config)
}
}
init { init {
nodeName.text = name INTEGER_FORMAT.isGroupingUsed = false
root.vgrow = Priority.ALWAYS
// Ensure that we close the terminal along with the tab.
nodeTab.setOnCloseRequest {
nodeTerminalView.close()
}
root.add(nodeConfigView)
root.add(nodeTerminalView)
model.p2pPort.value = controller.nextPort
model.artemisPort.value = controller.nextPort
model.webPort.value = controller.nextPort
} }
} }

View File

@ -0,0 +1,72 @@
package net.corda.demobench.views
import com.jediterm.terminal.TerminalColor
import com.jediterm.terminal.TextStyle
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider
import java.awt.Dimension
import javafx.embed.swing.SwingNode
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import javax.swing.SwingUtilities
import net.corda.demobench.model.NodeConfig
import net.corda.demobench.pty.R3Pty
import net.corda.demobench.ui.PropertyLabel
import tornadofx.Fragment
import tornadofx.vgrow
class NodeTerminalView : Fragment() {
override val root by fxml<VBox>()
private val nodeName by fxid<Label>()
private val p2pPort by fxid<PropertyLabel>()
private val states by fxid<PropertyLabel>()
private val transactions by fxid<PropertyLabel>()
private val balance by fxid<PropertyLabel>()
private val viewDatabaseButton by fxid<Button>()
private val launchExplorerButton by fxid<Button>()
var pty : R3Pty? = null
fun open(config: NodeConfig) {
nodeName.text = config.name
p2pPort.value = config.p2pPort.toString()
val swingTerminal = SwingNode()
swingTerminal.setOnMouseClicked {
swingTerminal.requestFocus()
}
root.children.add(swingTerminal)
root.isVisible = true
SwingUtilities.invokeLater({
val r3pty = R3Pty(config.name, TerminalSettingsProvider(), Dimension(160, 80))
pty = r3pty
swingTerminal.content = r3pty.terminal
r3pty.run("/bin/bash", "--login")
})
}
fun close() {
pty?.close()
}
init {
root.vgrow = Priority.ALWAYS
}
class TerminalSettingsProvider : DefaultSettingsProvider() {
override fun getDefaultStyle(): TextStyle {
return TextStyle(TerminalColor.WHITE, TerminalColor.BLACK)
}
override fun emulateX11CopyPaste(): Boolean {
return true
}
}
}

View File

@ -7,7 +7,7 @@
<?import javafx.scene.layout.VBox?> <?import javafx.scene.layout.VBox?>
<?import net.corda.demobench.ui.PropertyLabel?> <?import net.corda.demobench.ui.PropertyLabel?>
<VBox prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1"> <VBox visible="false" prefHeight="953.0" prefWidth="1363.0" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
<children> <children>
<HBox prefHeight="95.0" prefWidth="800.0" spacing="15.0" styleClass="header"> <HBox prefHeight="95.0" prefWidth="800.0" spacing="15.0" styleClass="header">
<children> <children>