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 {
ext.kotlin_version = '1.0.6'
ext.tornadofx_version = '1.6.0'
ext.jna_version = '4.1.0'
ext.purejavacomm_version = '0.0.17'
ext.guava_version = '14.0.1'
ext.slf4j_version = '1.7.22'
ext.logback_version = '1.1.3'
repositories {
mavenCentral()
@ -18,8 +20,11 @@ buildscript {
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
sourceCompatibility = 1.8
mainClassName = 'net.corda.demobench.DemoBench'
applicationDefaultJvmArgs = ['-Djava.util.logging.config.class=net.corda.demobench.config.LoggingConfig']
repositories {
flatDir {
@ -34,6 +39,8 @@ repositories {
}
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 "net.java.dev.jna:jna:$jna_version"
@ -42,12 +49,15 @@ dependencies {
compile "com.sparetimelabs:purejavacomm:$purejavacomm_version"
compile "org.slf4j:log4j-over-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 ':pty4j-0.7.2'
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
public void close() {
LOG.info("Closing terminal '{}'", name);
terminal.close();
if (terminal.getTerminalStarter() != null) {
terminal.close();
}
}
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
import com.jediterm.terminal.TerminalColor
import com.jediterm.terminal.TextStyle
import com.jediterm.terminal.ui.settings.DefaultSettingsProvider
import java.util.*
import javafx.application.Platform
import javafx.embed.swing.SwingNode
import javafx.scene.Parent
import javafx.scene.control.Button
import javafx.scene.control.Tab
import javafx.scene.control.TabPane
import javax.swing.SwingUtilities
import net.corda.demobench.pty.R3Pty
import net.corda.demobench.ui.CloseableTab
import tornadofx.*
@ -22,8 +16,6 @@ class DemoBenchView : View("Corda Demo Bench") {
val addNodeButton by fxid<Button>()
val nodeTabPane by fxid<TabPane>()
val settingsProvider = TerminalSettingsProvider()
init {
importStylesheet("/net/corda/demobench/style.css")
@ -35,7 +27,7 @@ class DemoBenchView : View("Corda Demo Bench") {
}
addNodeButton.setOnAction {
val nodeTab = createNode()
val nodeTab = createNodeTab()
nodeTabPane.selectionModel.select(nodeTab)
}
addNodeButton.fire()
@ -47,40 +39,12 @@ class DemoBenchView : View("Corda Demo Bench") {
}
}
fun createNode(): CloseableTab {
val pty = R3Pty("Banksy", settingsProvider, java.awt.Dimension(160, 80))
val nodeTabView = NodeTabView(pty.name)
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()
}
})
fun createNodeTab(): CloseableTab {
val nodeTabView = find<NodeTabView>()
val nodeTab = nodeTabView.nodeTab
nodeTabPane.tabs.add(nodeTab)
nodeTab.content.add(swingTerminal)
pty.run("/bin/bash", "--login")
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
import javafx.scene.control.Button
import javafx.scene.control.Label
import javafx.scene.layout.Priority
import javafx.scene.layout.VBox
import net.corda.demobench.ui.PropertyLabel
import tornadofx.Fragment
import tornadofx.vgrow
import java.text.DecimalFormat
import javafx.util.converter.NumberStringConverter
import net.corda.demobench.model.NodeController
import net.corda.demobench.model.NodeDataModel
import net.corda.demobench.ui.CloseableTab
import tornadofx.*
class NodeTabView(name: String) : Fragment() {
override val root by fxml<VBox>()
class NodeTabView : Fragment() {
override val root = stackpane {}
val nodeName by fxid<Label>()
val p2pPort by fxid<PropertyLabel>()
val states by fxid<PropertyLabel>()
val transactions by fxid<PropertyLabel>()
val balance by fxid<PropertyLabel>()
private val INTEGER_FORMAT = DecimalFormat()
private val NOT_NUMBER = Regex("[^\\d]")
val viewDatabaseButton by fxid<Button>()
val launchExplorerButton by fxid<Button>()
private val model = NodeDataModel()
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 {
nodeName.text = name
root.vgrow = Priority.ALWAYS
INTEGER_FORMAT.isGroupingUsed = false
// 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 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>
<HBox prefHeight="95.0" prefWidth="800.0" spacing="15.0" styleClass="header">
<children>