mirror of
https://github.com/corda/corda.git
synced 2025-02-27 19:46:38 +00:00
CORPRIV-661: Allow profiles to be loaded into DemoBench.
This commit is contained in:
parent
0ae5713a47
commit
38e57d6342
@ -6,6 +6,7 @@ buildscript {
|
||||
ext.guava_version = '14.0.1'
|
||||
ext.slf4j_version = '1.7.22'
|
||||
ext.logback_version = '1.1.10'
|
||||
ext.controlsfx_version = '8.40.12'
|
||||
|
||||
ext.java_home = System.properties.'java.home'
|
||||
ext.pkg_source = "$buildDir/packagesrc"
|
||||
@ -52,6 +53,9 @@ dependencies {
|
||||
compile "no.tornado:tornadofx:$tornadofx_version"
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
||||
|
||||
// Controls FX: more java FX components http://fxexperience.com/controlsfx/
|
||||
compile "org.controlsfx:controlsfx:$controlsfx_version"
|
||||
|
||||
// ONLY USING THE RPC CLIENT!?
|
||||
compile project(':node')
|
||||
|
||||
|
@ -8,11 +8,12 @@ import java.util.concurrent.Executors
|
||||
import kotlin.reflect.jvm.jvmName
|
||||
|
||||
class DBViewer : AutoCloseable {
|
||||
private val log = loggerFor<DBViewer>()
|
||||
private companion object {
|
||||
val log = loggerFor<DBViewer>()
|
||||
}
|
||||
|
||||
private val webServer: Server
|
||||
private val pool = Executors.newCachedThreadPool()
|
||||
private val t = Thread("DBViewer")
|
||||
|
||||
init {
|
||||
val ws = LocalWebServer()
|
||||
@ -23,15 +24,15 @@ class DBViewer : AutoCloseable {
|
||||
webServer.stop()
|
||||
}
|
||||
|
||||
t.run {
|
||||
pool.submit {
|
||||
webServer.start()
|
||||
}
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
webServer.shutdown()
|
||||
log.info("Shutting down")
|
||||
pool.shutdown()
|
||||
t.join()
|
||||
webServer.shutdown()
|
||||
}
|
||||
|
||||
fun openBrowser(h2Port: Int) {
|
||||
|
@ -4,7 +4,9 @@ import net.corda.demobench.loggerFor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
||||
private val log = loggerFor<Explorer>()
|
||||
private companion object {
|
||||
val log = loggerFor<Explorer>()
|
||||
}
|
||||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private var process: Process? = null
|
||||
@ -21,8 +23,8 @@ class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
||||
val p = explorerController.process(
|
||||
"--host=localhost",
|
||||
"--port=${config.artemisPort}",
|
||||
"--username=${config.user["user"]}",
|
||||
"--password=${config.user["password"]}",
|
||||
"--username=${config.users[0]["user"]}",
|
||||
"--password=${config.users[0]["password"]}",
|
||||
"--certificatesDir=${config.ssl.certificatesDirectory}",
|
||||
"--keyStorePassword=${config.ssl.keyStorePassword}",
|
||||
"--trustStorePassword=${config.ssl.trustStorePassword}")
|
||||
@ -51,9 +53,9 @@ class Explorer(val explorerController: ExplorerController) : AutoCloseable {
|
||||
process?.destroy()
|
||||
}
|
||||
|
||||
private fun safeClose(c: AutoCloseable?) {
|
||||
private fun safeClose(c: AutoCloseable) {
|
||||
try {
|
||||
c?.close()
|
||||
c.close()
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to close stream: '{}'", e.message)
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
package net.corda.demobench.model
|
||||
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import com.typesafe.config.ConfigValue
|
||||
import com.typesafe.config.ConfigValueFactory
|
||||
import net.corda.node.services.config.SSLConfiguration
|
||||
import com.typesafe.config.*
|
||||
import java.lang.String.join
|
||||
import java.nio.file.Path
|
||||
import net.corda.node.services.config.SSLConfiguration
|
||||
|
||||
class NodeConfig(
|
||||
baseDir: Path,
|
||||
@ -15,21 +12,26 @@ class NodeConfig(
|
||||
val nearestCity: String,
|
||||
val webPort: Int,
|
||||
val h2Port: Int,
|
||||
val extraServices: List<String>
|
||||
val extraServices: List<String>,
|
||||
val users: List<Map<String, Any>> = listOf(defaultUser)
|
||||
) : NetworkMapConfig(legalName, artemisPort) {
|
||||
|
||||
companion object {
|
||||
val renderOptions: ConfigRenderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||
|
||||
val defaultUser: Map<String, Any> = mapOf(
|
||||
"user" to "guest",
|
||||
"password" to "letmein",
|
||||
"permissions" to listOf(
|
||||
"StartFlow.net.corda.flows.CashFlow",
|
||||
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
val nodeDir: Path = baseDir.resolve(key)
|
||||
val explorerDir: Path = baseDir.resolve("$key-explorer")
|
||||
|
||||
val user: Map<String, Any> = mapOf(
|
||||
"user" to "guest",
|
||||
"password" to "letmein",
|
||||
"permissions" to listOf(
|
||||
"StartFlow.net.corda.flows.CashFlow",
|
||||
"StartFlow.net.corda.flows.IssuerFlow\$IssuanceRequester"
|
||||
)
|
||||
)
|
||||
|
||||
val ssl: SSLConfiguration = object : SSLConfiguration {
|
||||
override val certificatesDirectory: Path = nodeDir.resolve("certificates")
|
||||
override val trustStorePassword: String = "trustpass"
|
||||
@ -40,29 +42,35 @@ class NodeConfig(
|
||||
|
||||
var state: NodeState = NodeState.STARTING
|
||||
|
||||
/*
|
||||
* The configuration object depends upon the networkMap,
|
||||
* which is mutable.
|
||||
*/
|
||||
val toFileConfig: Config
|
||||
get() = ConfigFactory.empty()
|
||||
.withValue("myLegalName", valueFor(legalName))
|
||||
.withValue("artemisAddress", addressValueFor(artemisPort))
|
||||
.withValue("nearestCity", valueFor(nearestCity))
|
||||
.withValue("extraAdvertisedServiceIds", valueFor(join(",", extraServices)))
|
||||
.withFallback(optional("networkMapService", networkMap, {
|
||||
c, n -> c.withValue("address", addressValueFor(n.artemisPort))
|
||||
.withValue("legalName", valueFor(n.legalName))
|
||||
} ))
|
||||
.withValue("webAddress", addressValueFor(webPort))
|
||||
.withValue("rpcUsers", valueFor(listOf(user)))
|
||||
.withValue("h2port", valueFor(h2Port))
|
||||
.withValue("useTestClock", valueFor(true))
|
||||
|
||||
val isCashIssuer: Boolean = extraServices.any {
|
||||
it.startsWith("corda.issuer.")
|
||||
}
|
||||
|
||||
fun isNetworkMap(): Boolean = networkMap == null
|
||||
|
||||
/*
|
||||
* The configuration object depends upon the networkMap,
|
||||
* which is mutable.
|
||||
*/
|
||||
fun toFileConfig(): Config = ConfigFactory.empty()
|
||||
.withValue("myLegalName", valueFor(legalName))
|
||||
.withValue("artemisAddress", addressValueFor(artemisPort))
|
||||
.withValue("nearestCity", valueFor(nearestCity))
|
||||
.withValue("extraAdvertisedServiceIds", valueFor(join(",", extraServices)))
|
||||
.withFallback(optional("networkMapService", networkMap, {
|
||||
c, n -> c.withValue("address", addressValueFor(n.artemisPort))
|
||||
.withValue("legalName", valueFor(n.legalName))
|
||||
} ))
|
||||
.withValue("webAddress", addressValueFor(webPort))
|
||||
.withValue("rpcUsers", valueFor(users))
|
||||
.withValue("h2port", valueFor(h2Port))
|
||||
.withValue("useTestClock", valueFor(true))
|
||||
|
||||
fun toText() = toFileConfig().root().render(renderOptions)
|
||||
|
||||
fun moveTo(baseDir: Path) = NodeConfig(
|
||||
baseDir, legalName, artemisPort, nearestCity, webPort, h2Port, extraServices, users
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> valueFor(any: T): ConfigValue? = ConfigValueFactory.fromAnyRef(any)
|
||||
|
@ -1,18 +1,17 @@
|
||||
package net.corda.demobench.model
|
||||
|
||||
import com.typesafe.config.ConfigRenderOptions
|
||||
import java.io.IOException
|
||||
import java.lang.management.ManagementFactory
|
||||
import java.net.ServerSocket
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
import net.corda.demobench.pty.R3Pty
|
||||
import tornadofx.Controller
|
||||
import java.io.IOException
|
||||
import java.net.ServerSocket
|
||||
|
||||
class NodeController : Controller() {
|
||||
private companion object Data {
|
||||
private companion object {
|
||||
const val FIRST_PORT = 10000
|
||||
const val MIN_PORT = 1024
|
||||
const val MAX_PORT = 65535
|
||||
@ -20,9 +19,7 @@ class NodeController : Controller() {
|
||||
|
||||
private val jvm by inject<JVMConfig>()
|
||||
|
||||
private val localDir = SimpleDateFormat("yyyyMMddHHmmss")
|
||||
.format(Date(ManagementFactory.getRuntimeMXBean().startTime))
|
||||
private val baseDir = jvm.userHome.resolve("demobench").resolve(localDir)
|
||||
private var baseDir = baseDirFor(ManagementFactory.getRuntimeMXBean().startTime)
|
||||
private val pluginDir = jvm.applicationDir.resolve("plugins")
|
||||
|
||||
private val bankOfCorda = pluginDir.resolve("bank-of-corda.jar").toFile()
|
||||
@ -30,13 +27,15 @@ class NodeController : Controller() {
|
||||
private val cordaPath = jvm.applicationDir.resolve("corda").resolve("corda.jar")
|
||||
private val command = jvm.commandFor(cordaPath)
|
||||
|
||||
private val renderOptions = ConfigRenderOptions.defaults().setOriginComments(false)
|
||||
|
||||
private val nodes = ConcurrentHashMap<String, NodeConfig>()
|
||||
private val port = AtomicInteger(FIRST_PORT)
|
||||
|
||||
private var networkMapConfig: NetworkMapConfig? = null
|
||||
|
||||
val activeNodes: List<NodeConfig> get() = nodes.values.filter {
|
||||
it.state == NodeState.RUNNING
|
||||
}
|
||||
|
||||
init {
|
||||
log.info("Base directory: $baseDir")
|
||||
log.info("Corda JAR: $cordaPath")
|
||||
@ -75,7 +74,7 @@ class NodeController : Controller() {
|
||||
val nextPort: Int get() = port.andIncrement
|
||||
|
||||
fun isPortAvailable(port: Int): Boolean {
|
||||
if ((port >= MIN_PORT) && (port <= MAX_PORT)) {
|
||||
if (isPortValid(port)) {
|
||||
try {
|
||||
ServerSocket(port).close()
|
||||
return true
|
||||
@ -87,6 +86,8 @@ class NodeController : Controller() {
|
||||
}
|
||||
}
|
||||
|
||||
fun isPortValid(port: Int): Boolean = (port >= MIN_PORT) && (port <= MAX_PORT)
|
||||
|
||||
fun keyExists(key: String) = nodes.keys.contains(key)
|
||||
|
||||
fun nameExists(name: String) = keyExists(toKey(name))
|
||||
@ -105,17 +106,16 @@ class NodeController : Controller() {
|
||||
fun runCorda(pty: R3Pty, config: NodeConfig): Boolean {
|
||||
val nodeDir = config.nodeDir.toFile()
|
||||
|
||||
if (nodeDir.mkdirs()) {
|
||||
if (nodeDir.isDirectory || nodeDir.mkdirs()) {
|
||||
try {
|
||||
// Write this node's configuration file into its working directory.
|
||||
val confFile = nodeDir.resolve("node.conf")
|
||||
val fileData = config.toFileConfig
|
||||
confFile.writeText(fileData.root().render(renderOptions))
|
||||
confFile.writeText(config.toText())
|
||||
|
||||
// Nodes cannot issue cash unless they contain the "Bank of Corda" plugin.
|
||||
if (config.isCashIssuer && bankOfCorda.isFile) {
|
||||
log.info("Installing 'Bank of Corda' plugin")
|
||||
bankOfCorda.copyTo(nodeDir.resolve("plugins").resolve(bankOfCorda.name))
|
||||
bankOfCorda.copyTo(nodeDir.resolve("plugins").resolve(bankOfCorda.name), overwrite=true)
|
||||
}
|
||||
|
||||
// Execute the Corda node
|
||||
@ -131,4 +131,30 @@ class NodeController : Controller() {
|
||||
}
|
||||
}
|
||||
|
||||
fun reset() {
|
||||
baseDir = baseDirFor(System.currentTimeMillis())
|
||||
log.info("Changed base directory: $baseDir")
|
||||
|
||||
// Wipe out any knowledge of previous nodes.
|
||||
networkMapConfig = null
|
||||
nodes.clear()
|
||||
}
|
||||
|
||||
fun register(config: NodeConfig): Boolean {
|
||||
if (nodes.putIfAbsent(config.key, config) != null) {
|
||||
return false
|
||||
}
|
||||
|
||||
if ((networkMapConfig == null) && config.isNetworkMap()) {
|
||||
networkMapConfig = config
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun relocate(config: NodeConfig) = config.moveTo(baseDir)
|
||||
|
||||
private fun baseDirFor(time: Long) = jvm.userHome.resolve("demobench").resolve(localFor(time))
|
||||
private fun localFor(time: Long) = SimpleDateFormat("yyyyMMddHHmmss").format(Date(time))
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import net.corda.demobench.loggerFor
|
||||
import java.util.concurrent.Executors
|
||||
|
||||
class WebServer(val webServerController: WebServerController) : AutoCloseable {
|
||||
private val log = loggerFor<WebServer>()
|
||||
private companion object {
|
||||
val log = loggerFor<WebServer>()
|
||||
}
|
||||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
private var process: Process? = null
|
||||
@ -44,9 +46,9 @@ class WebServer(val webServerController: WebServerController) : AutoCloseable {
|
||||
process?.destroy()
|
||||
}
|
||||
|
||||
private fun safeClose(c: AutoCloseable?) {
|
||||
private fun safeClose(c: AutoCloseable) {
|
||||
try {
|
||||
c?.close()
|
||||
c.close()
|
||||
} catch (e: Exception) {
|
||||
log.error("Failed to close stream: '{}'", e.message)
|
||||
}
|
||||
|
@ -0,0 +1,110 @@
|
||||
package net.corda.demobench.profile
|
||||
|
||||
import com.google.common.net.HostAndPort
|
||||
import com.typesafe.config.Config
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import java.nio.file.FileSystems
|
||||
import java.nio.file.Files
|
||||
import java.nio.file.Path
|
||||
import java.util.*
|
||||
import javafx.stage.FileChooser
|
||||
import javafx.stage.FileChooser.ExtensionFilter
|
||||
import net.corda.demobench.model.*
|
||||
import tornadofx.Controller
|
||||
|
||||
class ProfileController : Controller() {
|
||||
|
||||
private val jvm by inject<JVMConfig>()
|
||||
private val baseDir = jvm.userHome.resolve("demobench")
|
||||
private val nodeController by inject<NodeController>()
|
||||
private val serviceController by inject<ServiceController>()
|
||||
private val chooser = FileChooser()
|
||||
|
||||
init {
|
||||
chooser.initialDirectory = baseDir.toFile()
|
||||
chooser.extensionFilters.add(ExtensionFilter("DemoBench profiles (*.zip)", "*.zip", "*.ZIP"))
|
||||
}
|
||||
|
||||
fun saveAs() {
|
||||
log.info("Save as")
|
||||
}
|
||||
|
||||
fun save() {
|
||||
log.info("Save")
|
||||
}
|
||||
|
||||
fun openProfile(): List<NodeConfig>? {
|
||||
val chosen = chooser.showOpenDialog(null) ?: return null
|
||||
log.info("Selected profile: ${chosen}")
|
||||
|
||||
val configs = LinkedList<NodeConfig>()
|
||||
|
||||
FileSystems.newFileSystem(chosen.toPath(), null).use {
|
||||
fs -> fs.rootDirectories.forEach {
|
||||
root -> Files.walk(root).forEach {
|
||||
if ((it.nameCount == 2) && ("node.conf" == it.fileName.toString())) {
|
||||
try {
|
||||
configs.add(toNodeConfig(parse(it)))
|
||||
} catch (e: Exception) {
|
||||
log.severe("Failed to parse '$it': ${e.message}")
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configs
|
||||
}
|
||||
|
||||
private fun toNodeConfig(config: Config): NodeConfig {
|
||||
val artemisPort = config.parsePort("artemisAddress")
|
||||
val webPort = config.parsePort("webAddress")
|
||||
val h2Port = config.getInt("h2port")
|
||||
val extraServices = config.parseExtraServices("extraAdvertisedServiceIds")
|
||||
|
||||
val nodeConfig = NodeConfig(
|
||||
baseDir, // temporary value
|
||||
config.getString("myLegalName"),
|
||||
artemisPort,
|
||||
config.getString("nearestCity"),
|
||||
webPort,
|
||||
h2Port,
|
||||
extraServices,
|
||||
config.getObjectList("rpcUsers").map { it.unwrapped() }.toList()
|
||||
)
|
||||
|
||||
if (config.hasPath("networkMapService")) {
|
||||
val nmap = config.getConfig("networkMapService")
|
||||
nodeConfig.networkMap = NetworkMapConfig(nmap.getString("legalName"), nmap.parsePort("address"))
|
||||
}
|
||||
|
||||
return nodeConfig
|
||||
}
|
||||
|
||||
private fun parse(path: Path): Config = Files.newBufferedReader(path).use {
|
||||
return ConfigFactory.parseReader(it)
|
||||
}
|
||||
|
||||
private fun Config.parsePort(path: String): Int {
|
||||
val address = this.getString(path)
|
||||
val port = HostAndPort.fromString(address).port
|
||||
if (!nodeController.isPortValid(port)) {
|
||||
throw IllegalArgumentException("Invalid port $port from '$path'.")
|
||||
}
|
||||
return port
|
||||
}
|
||||
|
||||
private fun Config.parseExtraServices(path: String): List<String> {
|
||||
val services = serviceController.services.toSortedSet()
|
||||
return this.getString(path).split(",").filter {
|
||||
!it.isNullOrEmpty()
|
||||
}.map {
|
||||
if (!services.contains(it)) {
|
||||
throw IllegalArgumentException("Unknown service '$it'.")
|
||||
} else {
|
||||
it
|
||||
}
|
||||
}.toList()
|
||||
}
|
||||
}
|
@ -13,7 +13,9 @@ import java.util.concurrent.Executors
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
class R3Pty(val name: String, settings: SettingsProvider, dimension: Dimension, val onExit: () -> Unit) : AutoCloseable {
|
||||
private val log = loggerFor<R3Pty>()
|
||||
private companion object {
|
||||
val log = loggerFor<R3Pty>()
|
||||
}
|
||||
|
||||
private val executor = Executors.newSingleThreadExecutor()
|
||||
|
||||
|
@ -9,10 +9,10 @@ import net.corda.demobench.model.NodeConfig
|
||||
import net.corda.node.services.messaging.CordaRPCClient
|
||||
|
||||
class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Unit): AutoCloseable {
|
||||
private val log = loggerFor<NodeRPC>()
|
||||
|
||||
companion object Data {
|
||||
private val ONE_SECOND = SECONDS.toMillis(1)
|
||||
private companion object Data {
|
||||
val log = loggerFor<NodeRPC>()
|
||||
val ONE_SECOND = SECONDS.toMillis(1)
|
||||
}
|
||||
|
||||
private val rpcClient = CordaRPCClient(HostAndPort.fromParts("localhost", config.artemisPort), config.ssl)
|
||||
@ -22,8 +22,8 @@ class NodeRPC(config: NodeConfig, start: () -> Unit, invoke: (CordaRPCOps) -> Un
|
||||
val setupTask = object : TimerTask() {
|
||||
override fun run() {
|
||||
try {
|
||||
rpcClient.start(config.user.getOrElse("user") { "none" } as String,
|
||||
config.user.getOrElse("password") { "none" } as String)
|
||||
rpcClient.start(config.users[0].getOrElse("user") { "none" } as String,
|
||||
config.users[0].getOrElse("password") { "none" } as String)
|
||||
val ops = rpcClient.proxy()
|
||||
|
||||
// Cancel the "setup" task now that we've created the RPC client.
|
||||
|
@ -4,17 +4,27 @@ import java.util.*
|
||||
import javafx.application.Platform
|
||||
import javafx.scene.Parent
|
||||
import javafx.scene.control.Button
|
||||
import javafx.scene.control.MenuItem
|
||||
import javafx.scene.control.Tab
|
||||
import javafx.scene.control.TabPane
|
||||
import net.corda.demobench.model.NodeConfig
|
||||
import net.corda.demobench.model.NodeController
|
||||
import net.corda.demobench.profile.ProfileController
|
||||
import net.corda.demobench.ui.CloseableTab
|
||||
import org.controlsfx.dialog.ExceptionDialog
|
||||
import tornadofx.*
|
||||
|
||||
class DemoBenchView : View("Corda Demo Bench") {
|
||||
|
||||
override val root by fxml<Parent>()
|
||||
|
||||
private val profileController by inject<ProfileController>()
|
||||
private val nodeController by inject<NodeController>()
|
||||
private val addNodeButton by fxid<Button>()
|
||||
private val nodeTabPane by fxid<TabPane>()
|
||||
private val menuOpen by fxid<MenuItem>()
|
||||
private val menuSave by fxid<MenuItem>()
|
||||
private val menuSaveAs by fxid<MenuItem>()
|
||||
|
||||
init {
|
||||
importStylesheet("/net/corda/demobench/style.css")
|
||||
@ -22,13 +32,33 @@ class DemoBenchView : View("Corda Demo Bench") {
|
||||
primaryStage.setOnCloseRequest {
|
||||
log.info("Exiting")
|
||||
|
||||
// Prevent any new NodeTabViews from being created.
|
||||
addNodeButton.isDisable = true
|
||||
|
||||
closeAllTabs()
|
||||
Platform.exit()
|
||||
}
|
||||
|
||||
menuSaveAs.setOnAction {
|
||||
profileController.saveAs()
|
||||
}
|
||||
menuSave.setOnAction {
|
||||
profileController.save()
|
||||
}
|
||||
menuOpen.setOnAction {
|
||||
try {
|
||||
val profile = profileController.openProfile()
|
||||
if (profile != null) {
|
||||
loadProfile(profile)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
ExceptionDialog(e).apply { initOwner(root.scene.window) }.showAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
addNodeButton.setOnAction {
|
||||
val nodeTab = createNodeTab()
|
||||
nodeTabPane.selectionModel.select(nodeTab)
|
||||
val nodeTabView = createNodeTabView(true)
|
||||
nodeTabPane.selectionModel.select(nodeTabView.nodeTab)
|
||||
|
||||
// Prevent us from creating new nodes until we have created the Network Map
|
||||
addNodeButton.isDisable = true
|
||||
@ -42,12 +72,22 @@ class DemoBenchView : View("Corda Demo Bench") {
|
||||
}
|
||||
}
|
||||
|
||||
fun createNodeTab(): CloseableTab {
|
||||
val nodeTabView = find<NodeTabView>()
|
||||
val nodeTab = nodeTabView.nodeTab
|
||||
fun createNodeTabView(showConfig: Boolean): NodeTabView {
|
||||
val nodeTabView = find<NodeTabView>(mapOf("showConfig" to showConfig))
|
||||
nodeTabPane.tabs.add(nodeTabView.nodeTab)
|
||||
return nodeTabView
|
||||
}
|
||||
|
||||
nodeTabPane.tabs.add(nodeTab)
|
||||
return nodeTab
|
||||
fun loadProfile(nodes: List<NodeConfig>) {
|
||||
closeAllTabs()
|
||||
nodeController.reset()
|
||||
|
||||
nodes.forEach {
|
||||
val nodeTabView = createNodeTabView(false)
|
||||
nodeTabView.launch(nodeController.relocate(it))
|
||||
}
|
||||
|
||||
enableAddNodes()
|
||||
}
|
||||
|
||||
fun enableAddNodes() {
|
||||
|
@ -15,8 +15,9 @@ class NodeTabView : Fragment() {
|
||||
override val root = stackpane {}
|
||||
|
||||
private val main by inject<DemoBenchView>()
|
||||
private val showConfig by param<Boolean>()
|
||||
|
||||
private companion object Data {
|
||||
private companion object {
|
||||
val INTEGER_FORMAT = DecimalFormat()
|
||||
val NOT_NUMBER = "[^\\d]".toRegex()
|
||||
}
|
||||
@ -27,6 +28,8 @@ class NodeTabView : Fragment() {
|
||||
|
||||
private val nodeTerminalView = find<NodeTerminalView>()
|
||||
private val nodeConfigView = stackpane {
|
||||
isVisible = showConfig
|
||||
|
||||
form {
|
||||
fieldset("Configuration") {
|
||||
field("Node Name") {
|
||||
@ -179,19 +182,34 @@ class NodeTabView : Fragment() {
|
||||
model.h2Port.value = nodeController.nextPort
|
||||
}
|
||||
|
||||
/**
|
||||
* Launches a Corda node that was configured via the form.
|
||||
*/
|
||||
fun launch() {
|
||||
model.commit()
|
||||
val config = nodeController.validate(model.item)
|
||||
if (config != null) {
|
||||
nodeConfigView.isVisible = false
|
||||
nodeTab.text = config.legalName
|
||||
nodeTerminalView.open(config, onExit = { onTerminalExit(config) })
|
||||
launchNode(config)
|
||||
}
|
||||
}
|
||||
|
||||
nodeTab.setOnSelectionChanged {
|
||||
if (nodeTab.isSelected) {
|
||||
// Doesn't work yet
|
||||
nodeTerminalView.refreshTerminal()
|
||||
}
|
||||
/**
|
||||
* Launches a preconfigured Corda node, e.g. from a saved profile.
|
||||
*/
|
||||
fun launch(config: NodeConfig) {
|
||||
nodeController.register(config)
|
||||
launchNode(config)
|
||||
}
|
||||
|
||||
private fun launchNode(config: NodeConfig) {
|
||||
nodeTab.text = config.legalName
|
||||
nodeTerminalView.open(config, onExit = { onTerminalExit(config) })
|
||||
|
||||
nodeTab.setOnSelectionChanged {
|
||||
if (nodeTab.isSelected) {
|
||||
// Doesn't work yet
|
||||
nodeTerminalView.refreshTerminal()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,19 +2,29 @@
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Button?>
|
||||
<?import javafx.scene.control.MenuBar?>
|
||||
<?import javafx.scene.control.Menu?>
|
||||
<?import javafx.scene.control.MenuItem?>
|
||||
<?import javafx.scene.control.TabPane?>
|
||||
<?import javafx.scene.layout.StackPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<StackPane xmlns="http://javafx.com/javafx/8.0.111" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<children>
|
||||
<TabPane fx:id="nodeTabPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0" tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0">
|
||||
<tabs>
|
||||
</tabs>
|
||||
</TabPane>
|
||||
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="add-node-button" text="Add Node" StackPane.alignment="TOP_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="5.0" top="5.0" />
|
||||
</StackPane.margin>
|
||||
</Button>
|
||||
</children>
|
||||
</StackPane>
|
||||
<VBox xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
|
||||
<MenuBar>
|
||||
<Menu text="File">
|
||||
<MenuItem fx:id="menuOpen" text="Open"/>
|
||||
<MenuItem fx:id="menuSave" text="Save"/>
|
||||
<MenuItem fx:id="menuSaveAs" text="Save As"/>
|
||||
</Menu>
|
||||
</MenuBar>
|
||||
<StackPane>
|
||||
<children>
|
||||
<TabPane fx:id="nodeTabPane" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" minHeight="444.0" minWidth="800.0" prefHeight="613.0" prefWidth="1231.0" tabClosingPolicy="UNAVAILABLE" tabMinHeight="30.0"/>
|
||||
<Button fx:id="addNodeButton" mnemonicParsing="false" styleClass="add-node-button" text="Add Node" StackPane.alignment="TOP_RIGHT">
|
||||
<StackPane.margin>
|
||||
<Insets right="5.0" top="5.0" />
|
||||
</StackPane.margin>
|
||||
</Button>
|
||||
</children>
|
||||
</StackPane>
|
||||
</VBox>
|
||||
|
Loading…
x
Reference in New Issue
Block a user