mirror of
https://github.com/corda/corda.git
synced 2025-02-21 17:56:54 +00:00
V2 gui for the bootstrapper (#3373)
* V2 gui * Misc small fixes and tweaks * V2 gui * More tweaks * fix horizontal resize issue
This commit is contained in:
parent
a768904e4e
commit
de6e78b4a3
@ -31,7 +31,6 @@ apply plugin: 'application'
|
|||||||
apply plugin: 'com.github.johnrengelman.shadow'
|
apply plugin: 'com.github.johnrengelman.shadow'
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
compile "com.microsoft.azure:azure:1.8.0"
|
compile "com.microsoft.azure:azure:1.8.0"
|
||||||
compile "com.github.docker-java:docker-java:3.0.6"
|
compile "com.github.docker-java:docker-java:3.0.6"
|
||||||
|
|
||||||
@ -50,8 +49,8 @@ dependencies {
|
|||||||
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
||||||
compile "no.tornado:tornadofx:$tornadofx_version"
|
compile "no.tornado:tornadofx:$tornadofx_version"
|
||||||
|
|
||||||
|
// ControlsFX: Extra controls for JavaFX.
|
||||||
compile "org.controlsfx:controlsfx:$controlsfx_version"
|
compile "org.controlsfx:controlsfx:$controlsfx_version"
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
shadowJar {
|
shadowJar {
|
||||||
|
@ -1,44 +1,34 @@
|
|||||||
@file:JvmName("Main")
|
@file:JvmName("Main")
|
||||||
|
|
||||||
package net.corda.bootstrapper
|
package net.corda.bootstrapper
|
||||||
|
|
||||||
|
import javafx.application.Application
|
||||||
import net.corda.bootstrapper.backends.Backend
|
import net.corda.bootstrapper.backends.Backend
|
||||||
import net.corda.bootstrapper.backends.Backend.BackendType.AZURE
|
import net.corda.bootstrapper.backends.Backend.BackendType.AZURE
|
||||||
import net.corda.bootstrapper.cli.AzureParser
|
import net.corda.bootstrapper.cli.AzureParser
|
||||||
import net.corda.bootstrapper.cli.CliParser
|
import net.corda.bootstrapper.cli.CliParser
|
||||||
import net.corda.bootstrapper.cli.CommandLineInterface
|
import net.corda.bootstrapper.cli.CommandLineInterface
|
||||||
import net.corda.bootstrapper.cli.GuiSwitch
|
|
||||||
import net.corda.bootstrapper.gui.Gui
|
import net.corda.bootstrapper.gui.Gui
|
||||||
import net.corda.bootstrapper.serialization.SerializationEngine
|
import net.corda.bootstrapper.serialization.SerializationEngine
|
||||||
import picocli.CommandLine
|
import picocli.CommandLine
|
||||||
|
|
||||||
|
val baseArgs = CliParser()
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
SerializationEngine.init()
|
SerializationEngine.init()
|
||||||
|
CommandLine(baseArgs).parse(*args)
|
||||||
|
|
||||||
val entryPointArgs = GuiSwitch();
|
if (baseArgs.gui) {
|
||||||
CommandLine(entryPointArgs).parse(*args)
|
Application.launch(Gui::class.java)
|
||||||
|
|
||||||
if (entryPointArgs.usageHelpRequested) {
|
|
||||||
CommandLine.usage(AzureParser(), System.out)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val argParser: CliParser = when (baseArgs.backendType) {
|
||||||
if (entryPointArgs.gui) {
|
AZURE -> {
|
||||||
Gui.main(args)
|
val azureArgs = AzureParser()
|
||||||
} else {
|
CommandLine(azureArgs).parse(*args)
|
||||||
val baseArgs = CliParser()
|
azureArgs
|
||||||
CommandLine(baseArgs).parse(*args)
|
|
||||||
val argParser: CliParser = when (baseArgs.backendType) {
|
|
||||||
AZURE -> {
|
|
||||||
val azureArgs = AzureParser()
|
|
||||||
CommandLine(azureArgs).parse(*args)
|
|
||||||
azureArgs
|
|
||||||
}
|
|
||||||
Backend.BackendType.LOCAL_DOCKER -> baseArgs
|
|
||||||
}
|
}
|
||||||
CommandLineInterface().run(argParser)
|
Backend.BackendType.LOCAL_DOCKER -> baseArgs
|
||||||
}
|
}
|
||||||
|
CommandLineInterface().run(argParser)
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,10 @@ interface NetworkBuilder {
|
|||||||
fun withBackendOptions(options: Map<String, String>): NetworkBuilder
|
fun withBackendOptions(options: Map<String, String>): NetworkBuilder
|
||||||
|
|
||||||
fun build(): CompletableFuture<Pair<List<NodeInstance>, Context>>
|
fun build(): CompletableFuture<Pair<List<NodeInstance>, Context>>
|
||||||
|
fun onNodeStartBuild(callback: (FoundNode) -> Unit): NetworkBuilder
|
||||||
|
fun onNodePushStart(callback: (BuiltNode) -> Unit): NetworkBuilder
|
||||||
|
fun onNodeInstancesRequested(callback: (List<NodeInstanceRequest>) -> Unit): NetworkBuilder
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private class NetworkBuilderImpl : NetworkBuilder {
|
private class NetworkBuilderImpl : NetworkBuilder {
|
||||||
@ -40,11 +44,18 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
@Volatile
|
@Volatile
|
||||||
private var onNodeCopiedCallback: ((CopiedNode) -> Unit) = {}
|
private var onNodeCopiedCallback: ((CopiedNode) -> Unit) = {}
|
||||||
@Volatile
|
@Volatile
|
||||||
|
private var onNodeBuildStartCallback: (FoundNode) -> Unit = {}
|
||||||
|
@Volatile
|
||||||
private var onNodeBuiltCallback: ((BuiltNode) -> Unit) = {}
|
private var onNodeBuiltCallback: ((BuiltNode) -> Unit) = {}
|
||||||
@Volatile
|
@Volatile
|
||||||
|
private var onNodePushStartCallback: ((BuiltNode) -> Unit) = {}
|
||||||
|
@Volatile
|
||||||
private var onNodePushedCallback: ((PushedNode) -> Unit) = {}
|
private var onNodePushedCallback: ((PushedNode) -> Unit) = {}
|
||||||
@Volatile
|
@Volatile
|
||||||
|
private var onNodeInstanceRequestedCallback: (List<NodeInstanceRequest>) -> Unit = {}
|
||||||
|
@Volatile
|
||||||
private var onNodeInstanceCallback: ((NodeInstance) -> Unit) = {}
|
private var onNodeInstanceCallback: ((NodeInstance) -> Unit) = {}
|
||||||
|
|
||||||
@Volatile
|
@Volatile
|
||||||
private var nodeCounts = mapOf<String, Int>()
|
private var nodeCounts = mapOf<String, Int>()
|
||||||
@Volatile
|
@Volatile
|
||||||
@ -67,6 +78,12 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override fun onNodeStartBuild(callback: (FoundNode) -> Unit): NetworkBuilder {
|
||||||
|
this.onNodeBuildStartCallback = callback
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNodeBuild(callback: (BuiltNode) -> Unit): NetworkBuilder {
|
override fun onNodeBuild(callback: (BuiltNode) -> Unit): NetworkBuilder {
|
||||||
this.onNodeBuiltCallback = callback
|
this.onNodeBuiltCallback = callback
|
||||||
return this
|
return this
|
||||||
@ -77,6 +94,11 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNodeInstancesRequested(callback: (List<NodeInstanceRequest>) -> Unit): NetworkBuilder {
|
||||||
|
this.onNodeInstanceRequestedCallback = callback
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
override fun onNodeInstance(callback: (NodeInstance) -> Unit): NetworkBuilder {
|
override fun onNodeInstance(callback: (NodeInstance) -> Unit): NetworkBuilder {
|
||||||
this.onNodeInstanceCallback = callback;
|
this.onNodeInstanceCallback = callback;
|
||||||
return this
|
return this
|
||||||
@ -107,6 +129,12 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
return this
|
return this
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onNodePushStart(callback: (BuiltNode) -> Unit): NetworkBuilder {
|
||||||
|
this.onNodePushStartCallback = callback;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
override fun build(): CompletableFuture<Pair<List<NodeInstance>, Context>> {
|
override fun build(): CompletableFuture<Pair<List<NodeInstance>, Context>> {
|
||||||
val cacheDir = File(workingDir, cacheDirName)
|
val cacheDir = File(workingDir, cacheDirName)
|
||||||
val baseDir = workingDir!!
|
val baseDir = workingDir!!
|
||||||
@ -132,6 +160,7 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
val notaryDiscoveryFuture = CompletableFuture.supplyAsync {
|
val notaryDiscoveryFuture = CompletableFuture.supplyAsync {
|
||||||
val copiedNotaries = notaryFinder.findNotaries()
|
val copiedNotaries = notaryFinder.findNotaries()
|
||||||
.map { foundNode: FoundNode ->
|
.map { foundNode: FoundNode ->
|
||||||
|
onNodeBuildStartCallback.invoke(foundNode)
|
||||||
notaryCopier.copyNotary(foundNode)
|
notaryCopier.copyNotary(foundNode)
|
||||||
}
|
}
|
||||||
volume.notariesForNetworkParams(copiedNotaries)
|
volume.notariesForNetworkParams(copiedNotaries)
|
||||||
@ -141,14 +170,15 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
val notariesFuture = notaryDiscoveryFuture.thenCompose { copiedNotaries ->
|
val notariesFuture = notaryDiscoveryFuture.thenCompose { copiedNotaries ->
|
||||||
copiedNotaries
|
copiedNotaries
|
||||||
.map { copiedNotary ->
|
.map { copiedNotary ->
|
||||||
nodeBuilder.buildNode(copiedNotary)
|
nodeBuilder.buildNode(copiedNotary).also(onNodeBuiltCallback)
|
||||||
}.map { builtNotary ->
|
}.map { builtNotary ->
|
||||||
nodePusher.pushNode(builtNotary)
|
onNodePushStartCallback(builtNotary)
|
||||||
|
nodePusher.pushNode(builtNotary).thenApply { it.also(onNodePushedCallback) }
|
||||||
}.map { pushedNotary ->
|
}.map { pushedNotary ->
|
||||||
pushedNotary.thenApplyAsync { nodeInstantiator.createInstanceRequest(it) }
|
pushedNotary.thenApplyAsync { nodeInstantiator.createInstanceRequest(it).also { onNodeInstanceRequestedCallback.invoke(listOf(it)) } }
|
||||||
}.map { instanceRequest ->
|
}.map { instanceRequest ->
|
||||||
instanceRequest.thenComposeAsync { request ->
|
instanceRequest.thenComposeAsync { request ->
|
||||||
nodeInstantiator.instantiateNotaryInstance(request)
|
nodeInstantiator.instantiateNotaryInstance(request).thenApply { it.also(onNodeInstanceCallback) }
|
||||||
}
|
}
|
||||||
}.toSingleFuture()
|
}.toSingleFuture()
|
||||||
}
|
}
|
||||||
@ -161,6 +191,7 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
it
|
it
|
||||||
}
|
}
|
||||||
}.map { copiedNode: CopiedNode ->
|
}.map { copiedNode: CopiedNode ->
|
||||||
|
onNodeBuildStartCallback.invoke(copiedNode)
|
||||||
nodeBuilder.buildNode(copiedNode).let {
|
nodeBuilder.buildNode(copiedNode).let {
|
||||||
onNodeBuiltCallback.invoke(it)
|
onNodeBuiltCallback.invoke(it)
|
||||||
it
|
it
|
||||||
@ -172,7 +203,8 @@ private class NetworkBuilderImpl : NetworkBuilder {
|
|||||||
}
|
}
|
||||||
}.map { pushedNode ->
|
}.map { pushedNode ->
|
||||||
pushedNode.thenApplyAsync {
|
pushedNode.thenApplyAsync {
|
||||||
nodeInstantiator.createInstanceRequests(it, nodeCount)
|
nodeInstantiator.createInstanceRequests(it, nodeCount).also(onNodeInstanceRequestedCallback)
|
||||||
|
|
||||||
}
|
}
|
||||||
}.map { instanceRequests ->
|
}.map { instanceRequests ->
|
||||||
instanceRequests.thenComposeAsync { requests ->
|
instanceRequests.thenComposeAsync { requests ->
|
||||||
|
@ -22,8 +22,16 @@ interface Backend {
|
|||||||
val instantiator: Instantiator
|
val instantiator: Instantiator
|
||||||
val volume: Volume
|
val volume: Volume
|
||||||
|
|
||||||
enum class BackendType {
|
enum class BackendType(val displayName: String) {
|
||||||
AZURE, LOCAL_DOCKER
|
|
||||||
|
AZURE("Azure Containers"), LOCAL_DOCKER("Local Docker");
|
||||||
|
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return this.displayName
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
operator fun component1(): ContainerPusher {
|
operator fun component1(): ContainerPusher {
|
||||||
|
@ -17,7 +17,7 @@ class CommandLineInterface {
|
|||||||
fun run(parsedArgs: CliParser) {
|
fun run(parsedArgs: CliParser) {
|
||||||
val baseDir = parsedArgs.baseDirectory
|
val baseDir = parsedArgs.baseDirectory
|
||||||
val cacheDir = File(baseDir, Constants.BOOTSTRAPPER_DIR_NAME)
|
val cacheDir = File(baseDir, Constants.BOOTSTRAPPER_DIR_NAME)
|
||||||
val networkName = parsedArgs.name
|
val networkName = parsedArgs.name ?: "corda-network"
|
||||||
val objectMapper = Constants.getContextMapper()
|
val objectMapper = Constants.getContextMapper()
|
||||||
val contextFile = File(cacheDir, "$networkName.yaml")
|
val contextFile = File(cacheDir, "$networkName.yaml")
|
||||||
if (parsedArgs.isNew()) {
|
if (parsedArgs.isNew()) {
|
||||||
|
@ -7,30 +7,20 @@ import picocli.CommandLine
|
|||||||
import picocli.CommandLine.Option
|
import picocli.CommandLine.Option
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class GuiSwitch {
|
open class CliParser {
|
||||||
|
@Option(names = ["-n", "--network-name"], description = ["The resource grouping to use"])
|
||||||
|
var name: String? = null
|
||||||
|
|
||||||
@Option(names = ["-h", "--help"], usageHelp = true, description = ["display this help message"])
|
@Option(names = ["-g", "--gui"], description = ["Run the graphical user interface"])
|
||||||
var usageHelpRequested: Boolean = false
|
|
||||||
|
|
||||||
@Option(names = ["-g", "--gui"], description = ["Run in Gui Mode"])
|
|
||||||
var gui = false
|
var gui = false
|
||||||
|
|
||||||
@CommandLine.Unmatched
|
|
||||||
var unmatched = arrayListOf<String>()
|
|
||||||
}
|
|
||||||
|
|
||||||
open class CliParser : GuiSwitch() {
|
|
||||||
|
|
||||||
@Option(names = ["-n", "--network-name"], description = ["The resource grouping to use"], required = true)
|
|
||||||
lateinit var name: String
|
|
||||||
|
|
||||||
@Option(names = ["-d", "--nodes-directory"], description = ["The directory to search for nodes in"])
|
@Option(names = ["-d", "--nodes-directory"], description = ["The directory to search for nodes in"])
|
||||||
var baseDirectory = File(System.getProperty("user.dir"))
|
var baseDirectory = File(System.getProperty("user.dir"))
|
||||||
|
|
||||||
@Option(names = ["-b", "--backend"], description = ["The backend to use when instantiating nodes"])
|
@Option(names = ["-b", "--backend"], description = ["The backend to use when instantiating nodes"])
|
||||||
var backendType: Backend.BackendType = Backend.BackendType.LOCAL_DOCKER
|
var backendType: Backend.BackendType = Backend.BackendType.LOCAL_DOCKER
|
||||||
|
|
||||||
@Option(names = ["-nodes"], split = ":", description = ["The number of each node to create NodeX:2 will create two instances of NodeX"])
|
@Option(names = ["--nodes"], split = ":", description = ["The number of each node to create. NodeX:2 will create two instances of NodeX"])
|
||||||
var nodes: MutableMap<String, Int> = hashMapOf()
|
var nodes: MutableMap<String, Int> = hashMapOf()
|
||||||
|
|
||||||
@Option(names = ["--add", "-a"])
|
@Option(names = ["--add", "-a"])
|
||||||
@ -43,11 +33,9 @@ open class CliParser : GuiSwitch() {
|
|||||||
open fun backendOptions(): Map<String, String> {
|
open fun backendOptions(): Map<String, String> {
|
||||||
return emptyMap()
|
return emptyMap()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class AzureParser : CliParser() {
|
class AzureParser : CliParser() {
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val regions = Region.values().map { it.name() to it }.toMap()
|
val regions = Region.values().map { it.name() to it }.toMap()
|
||||||
}
|
}
|
||||||
@ -64,5 +52,4 @@ class AzureParser : CliParser() {
|
|||||||
override fun backendOptions(): Map<String, String> {
|
override fun backendOptions(): Map<String, String> {
|
||||||
return mapOf(Constants.REGION_ARG_NAME to region.name())
|
return mapOf(Constants.REGION_ARG_NAME to region.name())
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
@ -2,128 +2,143 @@ package net.corda.bootstrapper.gui
|
|||||||
|
|
||||||
import com.microsoft.azure.management.resources.fluentcore.arm.Region
|
import com.microsoft.azure.management.resources.fluentcore.arm.Region
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.beans.property.SimpleStringProperty
|
||||||
|
import javafx.collections.ObservableListBase
|
||||||
import javafx.collections.transformation.SortedList
|
import javafx.collections.transformation.SortedList
|
||||||
import javafx.event.EventHandler
|
import javafx.event.EventHandler
|
||||||
import javafx.scene.control.ChoiceDialog
|
import javafx.fxml.FXML
|
||||||
import javafx.scene.control.TableView.CONSTRAINED_RESIZE_POLICY
|
import javafx.scene.control.*
|
||||||
import javafx.scene.control.TextInputDialog
|
|
||||||
import javafx.scene.input.MouseEvent
|
import javafx.scene.input.MouseEvent
|
||||||
|
import javafx.scene.layout.HBox
|
||||||
import javafx.scene.layout.Priority
|
import javafx.scene.layout.Priority
|
||||||
|
import javafx.scene.layout.VBox
|
||||||
import javafx.stage.DirectoryChooser
|
import javafx.stage.DirectoryChooser
|
||||||
import net.corda.bootstrapper.Constants
|
import net.corda.bootstrapper.Constants
|
||||||
import net.corda.bootstrapper.GuiUtils
|
import net.corda.bootstrapper.GuiUtils
|
||||||
import net.corda.bootstrapper.NetworkBuilder
|
import net.corda.bootstrapper.NetworkBuilder
|
||||||
import net.corda.bootstrapper.backends.Backend
|
import net.corda.bootstrapper.backends.Backend
|
||||||
|
import net.corda.bootstrapper.baseArgs
|
||||||
import net.corda.bootstrapper.context.Context
|
import net.corda.bootstrapper.context.Context
|
||||||
import net.corda.bootstrapper.nodes.*
|
import net.corda.bootstrapper.nodes.*
|
||||||
import net.corda.bootstrapper.notaries.NotaryFinder
|
import net.corda.bootstrapper.notaries.NotaryFinder
|
||||||
import org.apache.commons.lang3.RandomStringUtils
|
import org.apache.commons.lang3.RandomStringUtils
|
||||||
|
import org.controlsfx.control.SegmentedButton
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.CompletableFuture
|
import java.util.concurrent.CompletableFuture
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
import kotlin.Comparator
|
||||||
import kotlin.collections.ArrayList
|
import kotlin.collections.ArrayList
|
||||||
|
|
||||||
class BootstrapperView : View("Network Bootstrapper") {
|
class BootstrapperView : View("Corda Network Builder") {
|
||||||
|
|
||||||
val YAML_MAPPER = Constants.getContextMapper()
|
val YAML_MAPPER = Constants.getContextMapper()
|
||||||
|
override val root: VBox by fxml("/views/mainPane.fxml")
|
||||||
|
|
||||||
val controller: State by inject()
|
val controller: State by inject()
|
||||||
|
|
||||||
val textarea = textarea {
|
val localDockerBtn: ToggleButton by fxid()
|
||||||
maxWidth = Double.MAX_VALUE
|
val azureBtn: ToggleButton by fxid()
|
||||||
maxHeight = Double.MAX_VALUE
|
val nodeTableView: TableView<NodeTemplateInfo> by fxid()
|
||||||
}
|
val templateChoiceBox: ChoiceBox<String> by fxid()
|
||||||
|
val buildButton: Button by fxid()
|
||||||
|
val addInstanceButton: Button by fxid()
|
||||||
|
val infoTextArea: TextArea by fxid()
|
||||||
|
|
||||||
override val root = vbox {
|
init {
|
||||||
|
visuallyTweakBackendSelector()
|
||||||
|
|
||||||
menubar {
|
buildButton.run {
|
||||||
menu("File") {
|
enableWhen { controller.baseDir.isNotNull }
|
||||||
item("Open") {
|
action {
|
||||||
action {
|
var networkName = "corda-network"
|
||||||
selectNodeDirectory().thenAcceptAsync({ (notaries: List<FoundNode>, nodes: List<FoundNode>) ->
|
|
||||||
controller.nodes(nodes)
|
val selectedBackEnd = when {
|
||||||
controller.notaries(notaries)
|
azureBtn.isSelected -> Backend.BackendType.AZURE
|
||||||
})
|
localDockerBtn.isSelected -> Backend.BackendType.LOCAL_DOCKER
|
||||||
|
else -> kotlin.error("Unknown backend selected")
|
||||||
|
}
|
||||||
|
|
||||||
|
val backendParams = when (selectedBackEnd) {
|
||||||
|
Backend.BackendType.LOCAL_DOCKER -> {
|
||||||
|
emptyMap()
|
||||||
|
}
|
||||||
|
Backend.BackendType.AZURE -> {
|
||||||
|
val pair = setupAzureRegionOptions()
|
||||||
|
networkName = pair.second
|
||||||
|
pair.first
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
item("Build") {
|
val nodeCount = controller.foundNodes.map { it.id to it.count }.toMap()
|
||||||
enableWhen(controller.baseDir.isNotNull)
|
val result = NetworkBuilder.instance()
|
||||||
action {
|
.withBasedir(controller.baseDir.get())
|
||||||
controller.clear()
|
.withNetworkName(networkName)
|
||||||
val availableBackends = getAvailableBackends()
|
.onNodeStartBuild(controller::onBuild)
|
||||||
val backend = ChoiceDialog<Backend.BackendType>(availableBackends.first(), availableBackends).showAndWait()
|
.onNodeBuild(controller::addBuiltNode)
|
||||||
var networkName = "gui-network"
|
.onNodePushStart(controller::addBuiltNode)
|
||||||
backend.ifPresent { selectedBackEnd ->
|
.onNodePushed(controller::addPushedNode)
|
||||||
|
.onNodeInstancesRequested(controller::addInstanceRequests)
|
||||||
|
.onNodeInstance(controller::addInstance)
|
||||||
|
.withBackend(selectedBackEnd)
|
||||||
|
.withNodeCounts(nodeCount)
|
||||||
|
.withBackendOptions(backendParams)
|
||||||
|
.build()
|
||||||
|
|
||||||
val backendParams = when (selectedBackEnd) {
|
result.handle { v, t ->
|
||||||
Backend.BackendType.LOCAL_DOCKER -> {
|
runLater {
|
||||||
|
if (t != null) {
|
||||||
|
GuiUtils.showException("Failed to build network", "Failure due to", t)
|
||||||
|
} else {
|
||||||
|
controller.networkContext.set(v.second)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
emptyMap<String, String>()
|
templateChoiceBox.run {
|
||||||
}
|
enableWhen { controller.networkContext.isNotNull }
|
||||||
Backend.BackendType.AZURE -> {
|
controller.networkContext.addListener { _, _, newValue ->
|
||||||
val defaultName = RandomStringUtils.randomAlphabetic(4) + "-network"
|
if (newValue != null) {
|
||||||
val textInputDialog = TextInputDialog(defaultName)
|
items = object : ObservableListBase<String>() {
|
||||||
textInputDialog.title = "Choose Network Name"
|
override fun get(index: Int): String {
|
||||||
networkName = textInputDialog.showAndWait().orElseGet { defaultName }
|
return controller.foundNodes[index].id
|
||||||
mapOf(Constants.REGION_ARG_NAME to ChoiceDialog<Region>(Region.EUROPE_WEST, Region.values().toList().sortedBy { it.name() }).showAndWait().get().name())
|
}
|
||||||
}
|
|
||||||
|
override val size: Int
|
||||||
|
get() = controller.foundNodes.size
|
||||||
|
}
|
||||||
|
selectionModel.select(controller.foundNodes[0].id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addInstanceButton.run {
|
||||||
|
enableWhen { controller.networkContext.isNotNull }
|
||||||
|
action {
|
||||||
|
templateChoiceBox.selectionModel.selectedItem?.let { nodeToAdd ->
|
||||||
|
val context = controller.networkContext.value
|
||||||
|
runLater {
|
||||||
|
val (_, instantiator, _) = Backend.fromContext(
|
||||||
|
context,
|
||||||
|
File(controller.baseDir.get(), Constants.BOOTSTRAPPER_DIR_NAME))
|
||||||
|
val nodeAdder = NodeAdder(context, NodeInstantiator(instantiator, context))
|
||||||
|
controller.addInstanceRequest(nodeToAdd)
|
||||||
|
nodeAdder.addNode(context, nodeToAdd).handleAsync { instanceInfo, t ->
|
||||||
|
t?.let {
|
||||||
|
GuiUtils.showException("Failed", "Failed to add node", it)
|
||||||
}
|
}
|
||||||
|
instanceInfo?.let {
|
||||||
val nodeCount = controller.foundNodes.map { it.id to it.count }.toMap()
|
|
||||||
val result = NetworkBuilder.instance()
|
|
||||||
.withBasedir(controller.baseDir.get())
|
|
||||||
.withNetworkName(networkName)
|
|
||||||
.onNodeBuild(controller::addBuiltNode)
|
|
||||||
.onNodePushed(controller::addPushedNode)
|
|
||||||
.onNodeInstance(controller::addInstance)
|
|
||||||
.withBackend(selectedBackEnd)
|
|
||||||
.withNodeCounts(nodeCount)
|
|
||||||
.withBackendOptions(backendParams)
|
|
||||||
.build()
|
|
||||||
result.handle { v, t ->
|
|
||||||
runLater {
|
runLater {
|
||||||
if (t != null) {
|
controller.addInstance(NodeInstanceEntry(
|
||||||
GuiUtils.showException("Failed to build network", "Failure due to", t)
|
it.groupId,
|
||||||
} else {
|
it.instanceName,
|
||||||
controller.networkContext.set(v.second)
|
it.instanceAddress,
|
||||||
}
|
it.reachableAddress,
|
||||||
}
|
it.portMapping[Constants.NODE_P2P_PORT] ?: Constants.NODE_P2P_PORT,
|
||||||
}
|
it.portMapping[Constants.NODE_SSHD_PORT]
|
||||||
}
|
?: Constants.NODE_SSHD_PORT))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item("Add Node") {
|
|
||||||
enableWhen(controller.networkContext.isNotNull)
|
|
||||||
action {
|
|
||||||
val foundNodes = controller.foundNodes.map { it.id }
|
|
||||||
val nodeToAdd = ChoiceDialog<String>(foundNodes.first(), *foundNodes.toTypedArray()).showAndWait()
|
|
||||||
val context = controller.networkContext.value
|
|
||||||
nodeToAdd.ifPresent { node ->
|
|
||||||
runLater {
|
|
||||||
val (_, instantiator, _) = Backend.fromContext(
|
|
||||||
context,
|
|
||||||
File(controller.baseDir.get(), Constants.BOOTSTRAPPER_DIR_NAME))
|
|
||||||
val nodeAdder = NodeAdder(context, NodeInstantiator(instantiator, context))
|
|
||||||
nodeAdder.addNode(context, node).handleAsync { instanceInfo, t ->
|
|
||||||
t?.let {
|
|
||||||
GuiUtils.showException("Failed", "Failed to add node", it)
|
|
||||||
}
|
|
||||||
instanceInfo?.let {
|
|
||||||
runLater {
|
|
||||||
controller.addInstance(NodeInstanceTableEntry(
|
|
||||||
it.groupId,
|
|
||||||
it.instanceName,
|
|
||||||
it.instanceAddress,
|
|
||||||
it.reachableAddress,
|
|
||||||
it.portMapping[Constants.NODE_P2P_PORT] ?: Constants.NODE_P2P_PORT,
|
|
||||||
it.portMapping[Constants.NODE_SSHD_PORT]
|
|
||||||
?: Constants.NODE_SSHD_PORT))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -132,89 +147,72 @@ class BootstrapperView : View("Network Bootstrapper") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
hbox {
|
nodeTableView.run {
|
||||||
vbox {
|
items = controller.sortedNodes
|
||||||
label("Nodes to build")
|
column("ID", NodeTemplateInfo::templateId)
|
||||||
val foundNodesTable = tableview(controller.foundNodes) {
|
column("Type", NodeTemplateInfo::nodeType)
|
||||||
readonlyColumn("ID", FoundNodeTableEntry::id)
|
column("Local Docker Image", NodeTemplateInfo::localDockerImageId)
|
||||||
column("Count", FoundNodeTableEntry::count).makeEditable()
|
column("Repository Image", NodeTemplateInfo::repositoryImageId)
|
||||||
vgrow = Priority.ALWAYS
|
column("Status", NodeTemplateInfo::status)
|
||||||
hgrow = Priority.ALWAYS
|
columnResizePolicy = TableView.CONSTRAINED_RESIZE_POLICY
|
||||||
}
|
|
||||||
foundNodesTable.columnResizePolicy = CONSTRAINED_RESIZE_POLICY
|
|
||||||
label("Notaries to build")
|
|
||||||
val notaryListView = listview(controller.foundNotaries) {
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
notaryListView.cellFormat { text = it.name }
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
vbox {
|
|
||||||
|
|
||||||
label("Built Nodes")
|
|
||||||
tableview(controller.builtNodes) {
|
|
||||||
readonlyColumn("ID", BuiltNodeTableEntry::id)
|
|
||||||
readonlyColumn("LocalImageId", BuiltNodeTableEntry::localImageId)
|
|
||||||
columnResizePolicy = CONSTRAINED_RESIZE_POLICY
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
label("Pushed Nodes")
|
|
||||||
tableview(controller.pushedNodes) {
|
|
||||||
readonlyColumn("ID", PushedNode::name)
|
|
||||||
readonlyColumn("RemoteImageId", PushedNode::remoteImageName)
|
|
||||||
columnResizePolicy = CONSTRAINED_RESIZE_POLICY
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
borderpane {
|
|
||||||
top = vbox {
|
|
||||||
label("Instances")
|
|
||||||
tableview(controller.nodeInstances) {
|
|
||||||
onMouseClicked = EventHandler<MouseEvent> { _ ->
|
|
||||||
textarea.text = YAML_MAPPER.writeValueAsString(selectionModel.selectedItem)
|
|
||||||
}
|
|
||||||
readonlyColumn("ID", NodeInstanceTableEntry::id)
|
|
||||||
readonlyColumn("InstanceId", NodeInstanceTableEntry::nodeInstanceName)
|
|
||||||
readonlyColumn("Address", NodeInstanceTableEntry::address)
|
|
||||||
columnResizePolicy = CONSTRAINED_RESIZE_POLICY
|
|
||||||
}
|
|
||||||
}
|
|
||||||
center = textarea
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
|
||||||
}
|
|
||||||
|
|
||||||
vgrow = Priority.ALWAYS
|
|
||||||
hgrow = Priority.ALWAYS
|
hgrow = Priority.ALWAYS
|
||||||
|
|
||||||
|
onMouseClicked = EventHandler<MouseEvent> { _ ->
|
||||||
|
val selectedItem: NodeTemplateInfo = selectionModel.selectedItem ?: return@EventHandler
|
||||||
|
infoTextArea.text = YAML_MAPPER.writeValueAsString(translateForPrinting(selectedItem))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
processSelectedDirectory(baseArgs.baseDirectory)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
e.printStackTrace()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getAvailableBackends(): List<Backend.BackendType> {
|
private fun visuallyTweakBackendSelector() {
|
||||||
return Backend.BackendType.values().toMutableList();
|
// The SegmentedButton will jam together the two toggle buttons in a way
|
||||||
|
// that looks more modern.
|
||||||
|
val hBox = localDockerBtn.parent as HBox
|
||||||
|
val idx = hBox.children.indexOf(localDockerBtn)
|
||||||
|
// Adding this to the hbox will re-parent the two toggle buttons into the
|
||||||
|
// SegmentedButton control, so we have to put it in the same position as
|
||||||
|
// the original buttons. Unfortunately it's not so Scene Builder friendly.
|
||||||
|
hBox.children.add(idx, SegmentedButton(localDockerBtn, azureBtn).apply {
|
||||||
|
styleClass.add(SegmentedButton.STYLE_CLASS_DARK)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun setupAzureRegionOptions(): Pair<Map<String, String>, String> {
|
||||||
fun selectNodeDirectory(): CompletableFuture<Pair<List<FoundNode>, List<FoundNode>>> {
|
var networkName1 = RandomStringUtils.randomAlphabetic(4) + "-network"
|
||||||
val fileChooser = DirectoryChooser();
|
val textInputDialog = TextInputDialog(networkName1)
|
||||||
fileChooser.initialDirectory = File(System.getProperty("user.home"))
|
textInputDialog.title = "Azure Resource Group"
|
||||||
val file = fileChooser.showDialog(null)
|
networkName1 = textInputDialog.showAndWait().orElseGet { networkName1 }
|
||||||
controller.baseDir.set(file)
|
return Pair(mapOf(Constants.REGION_ARG_NAME to ChoiceDialog<Region>(Region.EUROPE_WEST, Region.values().toList().sortedBy { it.name() }).showAndWait().get().name()), networkName1)
|
||||||
return processSelectedDirectory(file)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun translateForPrinting(selectedItem: NodeTemplateInfo): Any {
|
||||||
|
return object {
|
||||||
|
val templateId = selectedItem.templateId.get()
|
||||||
|
val nodeType = selectedItem.nodeType.get()
|
||||||
|
val localDockerImageId = selectedItem.localDockerImageId.get()
|
||||||
|
val repositoryImageId = selectedItem.repositoryImageId.get()
|
||||||
|
val status = selectedItem.status.get()
|
||||||
|
val instances = selectedItem.instances.map { it }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun processSelectedDirectory(dir: File): CompletableFuture<Pair<List<FoundNode>, List<FoundNode>>> {
|
@FXML
|
||||||
|
fun onOpenClicked() {
|
||||||
|
val chooser = DirectoryChooser()
|
||||||
|
chooser.initialDirectory = File(System.getProperty("user.home"))
|
||||||
|
val file: File = chooser.showDialog(null) ?: return // Null means user cancelled.
|
||||||
|
processSelectedDirectory(file)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun processSelectedDirectory(dir: File) {
|
||||||
|
controller.clearAll()
|
||||||
|
controller.baseDir.set(dir)
|
||||||
val foundNodes = CompletableFuture.supplyAsync {
|
val foundNodes = CompletableFuture.supplyAsync {
|
||||||
val nodeFinder = NodeFinder(dir)
|
val nodeFinder = NodeFinder(dir)
|
||||||
nodeFinder.findNodes()
|
nodeFinder.findNodes()
|
||||||
@ -223,101 +221,148 @@ class BootstrapperView : View("Network Bootstrapper") {
|
|||||||
val notaryFinder = NotaryFinder(dir)
|
val notaryFinder = NotaryFinder(dir)
|
||||||
notaryFinder.findNotaries()
|
notaryFinder.findNotaries()
|
||||||
}
|
}
|
||||||
return foundNodes.thenCombine(foundNotaries) { nodes, notaries ->
|
foundNodes.thenCombine(foundNotaries) { nodes, notaries ->
|
||||||
notaries to nodes
|
notaries to nodes
|
||||||
|
}.thenAcceptAsync({ (notaries: List<FoundNode>, nodes: List<FoundNode>) ->
|
||||||
|
runLater {
|
||||||
|
controller.foundNodes(nodes)
|
||||||
|
controller.notaries(notaries)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
class NodeTemplateInfo(templateId: String, type: NodeType) {
|
||||||
|
val templateId: SimpleStringProperty = object : SimpleStringProperty(templateId) {
|
||||||
|
override fun toString(): String {
|
||||||
|
return this.get()?.toString() ?: "null"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
val nodeType: SimpleObjectProperty<NodeType> = SimpleObjectProperty(type)
|
||||||
}
|
val localDockerImageId: SimpleStringProperty = SimpleStringProperty()
|
||||||
|
val repositoryImageId: SimpleStringProperty = SimpleStringProperty()
|
||||||
class State : Controller() {
|
val status: SimpleObjectProperty<NodeBuildStatus> = SimpleObjectProperty(NodeBuildStatus.DISCOVERED)
|
||||||
|
val instances: MutableList<NodeInstanceEntry> = ArrayList()
|
||||||
val foundNodes = Collections.synchronizedList(ArrayList<FoundNodeTableEntry>()).observable()
|
val numberOfInstancesWaiting: AtomicInteger = AtomicInteger(-1)
|
||||||
val builtNodes = Collections.synchronizedList(ArrayList<BuiltNodeTableEntry>()).observable()
|
|
||||||
val pushedNodes = Collections.synchronizedList(ArrayList<PushedNode>()).observable()
|
|
||||||
|
|
||||||
private val backingUnsortedInstances = Collections.synchronizedList(ArrayList<NodeInstanceTableEntry>()).observable()
|
|
||||||
val nodeInstances = SortedList(backingUnsortedInstances, COMPARATOR)
|
|
||||||
|
|
||||||
val foundNotaries = Collections.synchronizedList(ArrayList<FoundNode>()).observable()
|
|
||||||
val networkContext = SimpleObjectProperty<Context>(null)
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
builtNodes.clear()
|
|
||||||
pushedNodes.clear()
|
|
||||||
backingUnsortedInstances.clear()
|
|
||||||
networkContext.set(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun nodes(nodes: List<FoundNode>) {
|
enum class NodeBuildStatus {
|
||||||
foundNodes.clear()
|
DISCOVERED, LOCALLY_BUILDING, LOCALLY_BUILT, REMOTE_PUSHING, REMOTE_PUSHED, INSTANTIATING, INSTANTIATED,
|
||||||
nodes.forEach { addFoundNode(it) }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun notaries(notaries: List<FoundNode>) {
|
enum class NodeType {
|
||||||
foundNotaries.clear()
|
NODE, NOTARY
|
||||||
notaries.forEach { runLater { foundNotaries.add(it) } }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var baseDir = SimpleObjectProperty<File>(null)
|
class State : Controller() {
|
||||||
|
val foundNodes = Collections.synchronizedList(ArrayList<FoundNodeTableEntry>()).observable()
|
||||||
|
val foundNotaries = Collections.synchronizedList(ArrayList<FoundNode>()).observable()
|
||||||
|
val networkContext = SimpleObjectProperty<Context>(null)
|
||||||
|
|
||||||
|
val unsortedNodes = Collections.synchronizedList(ArrayList<NodeTemplateInfo>()).observable()
|
||||||
|
val sortedNodes = SortedList(unsortedNodes, Comparator<NodeTemplateInfo> { o1, o2 ->
|
||||||
|
compareValues(o1.nodeType.toString() + o1.templateId, o2.nodeType.toString() + o2.templateId) * -1
|
||||||
|
})
|
||||||
|
|
||||||
fun addFoundNode(foundNode: FoundNode) {
|
fun clear() {
|
||||||
runLater {
|
networkContext.set(null)
|
||||||
foundNodes.add(FoundNodeTableEntry(foundNode.name))
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun addBuiltNode(builtNode: BuiltNode) {
|
fun clearAll() {
|
||||||
runLater {
|
networkContext.set(null)
|
||||||
builtNodes.add(BuiltNodeTableEntry(builtNode.name, builtNode.localImageId))
|
foundNodes.clear()
|
||||||
|
foundNotaries.clear()
|
||||||
|
unsortedNodes.clear()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun addPushedNode(pushedNode: PushedNode) {
|
fun foundNodes(nodesToAdd: List<FoundNode>) {
|
||||||
runLater {
|
foundNodes.clear()
|
||||||
pushedNodes.add(pushedNode)
|
nodesToAdd.forEach {
|
||||||
|
runLater {
|
||||||
|
foundNodes.add(FoundNodeTableEntry(it.name))
|
||||||
|
unsortedNodes.add(NodeTemplateInfo(it.name, NodeType.NODE))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun addInstance(nodeInstance: NodeInstance) {
|
fun notaries(notaries: List<FoundNode>) {
|
||||||
runLater {
|
foundNotaries.clear()
|
||||||
backingUnsortedInstances.add(NodeInstanceTableEntry(
|
notaries.forEach {
|
||||||
|
runLater {
|
||||||
|
foundNotaries.add(it)
|
||||||
|
unsortedNodes.add(NodeTemplateInfo(it.name, NodeType.NOTARY))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
var baseDir = SimpleObjectProperty<File>(null)
|
||||||
|
|
||||||
|
fun addBuiltNode(builtNode: BuiltNode) {
|
||||||
|
runLater {
|
||||||
|
val foundNode = unsortedNodes.find { it.templateId.get() == builtNode.name }
|
||||||
|
foundNode?.status?.set(NodeBuildStatus.LOCALLY_BUILT)
|
||||||
|
foundNode?.localDockerImageId?.set(builtNode.localImageId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addPushedNode(pushedNode: PushedNode) {
|
||||||
|
runLater {
|
||||||
|
val foundNode = unsortedNodes.find { it.templateId.get() == pushedNode.name }
|
||||||
|
foundNode?.status?.set(NodeBuildStatus.REMOTE_PUSHED)
|
||||||
|
foundNode?.repositoryImageId?.set(pushedNode.remoteImageName)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onBuild(nodeBuilding: FoundNode) {
|
||||||
|
val foundNode = unsortedNodes.find { it.templateId.get() == nodeBuilding.name }
|
||||||
|
foundNode?.status?.set(NodeBuildStatus.LOCALLY_BUILDING)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addInstance(nodeInstance: NodeInstance) {
|
||||||
|
addInstance(NodeInstanceEntry(
|
||||||
nodeInstance.name,
|
nodeInstance.name,
|
||||||
nodeInstance.nodeInstanceName,
|
nodeInstance.nodeInstanceName,
|
||||||
nodeInstance.expectedFqName,
|
nodeInstance.expectedFqName,
|
||||||
nodeInstance.reachableAddress,
|
nodeInstance.reachableAddress,
|
||||||
nodeInstance.portMapping[Constants.NODE_P2P_PORT] ?: Constants.NODE_P2P_PORT,
|
nodeInstance.portMapping[Constants.NODE_P2P_PORT] ?: Constants.NODE_P2P_PORT,
|
||||||
nodeInstance.portMapping[Constants.NODE_SSHD_PORT] ?: Constants.NODE_SSHD_PORT))
|
nodeInstance.portMapping[Constants.NODE_SSHD_PORT] ?: Constants.NODE_SSHD_PORT)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
fun addInstance(nodeInstance: NodeInstanceTableEntry) {
|
fun addInstanceRequests(requests: List<NodeInstanceRequest>) {
|
||||||
runLater {
|
requests.firstOrNull()?.let { request ->
|
||||||
backingUnsortedInstances.add(nodeInstance)
|
unsortedNodes.find { it.templateId.get() == request.name }?.let {
|
||||||
}
|
it.numberOfInstancesWaiting.set(requests.size)
|
||||||
}
|
it.status.set(NodeBuildStatus.INSTANTIATING)
|
||||||
|
}
|
||||||
companion object {
|
|
||||||
val COMPARATOR: (NodeInstanceTableEntry, NodeInstanceTableEntry) -> Int = { o1, o2 ->
|
|
||||||
if (o1.id == (o2.id)) {
|
|
||||||
o1.nodeInstanceName.compareTo(o2.nodeInstanceName)
|
|
||||||
} else {
|
|
||||||
o1.id.compareTo(o2.id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun addInstance(nodeInstance: NodeInstanceEntry) {
|
||||||
|
runLater {
|
||||||
|
val foundNode = unsortedNodes.find { it.templateId.get() == nodeInstance.id }
|
||||||
|
foundNode?.instances?.add(nodeInstance)
|
||||||
|
if (foundNode != null && foundNode.instances.size == foundNode.numberOfInstancesWaiting.get()) {
|
||||||
|
foundNode.status.set(NodeBuildStatus.INSTANTIATED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addInstanceRequest(nodeToAdd: String) {
|
||||||
|
val foundNode = unsortedNodes.find { it.templateId.get() == nodeToAdd }
|
||||||
|
foundNode?.numberOfInstancesWaiting?.incrementAndGet()
|
||||||
|
foundNode?.status?.set(NodeBuildStatus.INSTANTIATING)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
data class NodeInstanceEntry(val id: String,
|
||||||
|
val nodeInstanceName: String,
|
||||||
|
val address: String,
|
||||||
|
val locallyReachableAddress: String,
|
||||||
|
val rpcPort: Int,
|
||||||
|
val sshPort: Int)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FoundNodeTableEntry(val id: String,
|
data class FoundNodeTableEntry(val id: String, @Volatile var count: Int = 1)
|
||||||
@Volatile var count: Int = 1)
|
|
||||||
|
|
||||||
data class BuiltNodeTableEntry(val id: String, val localImageId: String)
|
|
||||||
|
|
||||||
data class NodeInstanceTableEntry(val id: String,
|
|
||||||
val nodeInstanceName: String,
|
|
||||||
val address: String,
|
|
||||||
val locallyReachableAddress: String,
|
|
||||||
val rpcPort: Int,
|
|
||||||
val sshPort: Int)
|
|
@ -1,11 +1,11 @@
|
|||||||
package net.corda.bootstrapper.gui
|
package net.corda.bootstrapper.gui
|
||||||
|
|
||||||
import javafx.application.Application
|
import javafx.stage.Stage
|
||||||
import tornadofx.App
|
import tornadofx.*
|
||||||
|
|
||||||
class Gui : App(BootstrapperView::class) {
|
class Gui : App(BootstrapperView::class) {
|
||||||
companion object {
|
override fun start(stage: Stage) {
|
||||||
@JvmStatic
|
super.start(stage)
|
||||||
fun main(args: Array<String>) = Application.launch(Gui::class.java, *args)
|
stage.scene.stylesheets.add("/views/bootstrapper.css")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
13
tools/network-bootstrapper/src/main/resources/views/bootstrapper.css
vendored
Normal file
13
tools/network-bootstrapper/src/main/resources/views/bootstrapper.css
vendored
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
.top-pane {
|
||||||
|
-fx-background-color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-pane > .button, .top-pane .toggle-button {
|
||||||
|
-fx-padding: 15px;
|
||||||
|
-fx-base: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.top-pane > .choice-box {
|
||||||
|
-fx-padding: 10px;
|
||||||
|
-fx-base: white;
|
||||||
|
}
|
Binary file not shown.
After Width: | Height: | Size: 32 KiB |
@ -0,0 +1,53 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<VBox prefHeight="768.0" prefWidth="1024.0" xmlns="http://javafx.com/javafx/8.0.171" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<children>
|
||||||
|
<HBox alignment="CENTER_LEFT" minHeight="-Infinity" prefHeight="75.0" prefWidth="1073.0" styleClass="top-pane">
|
||||||
|
<children>
|
||||||
|
<ImageView fitHeight="150.0" fitWidth="100.0" pickOnBounds="true" preserveRatio="true">
|
||||||
|
<image>
|
||||||
|
<Image url="@cordalogo.png" />
|
||||||
|
</image>
|
||||||
|
</ImageView>
|
||||||
|
<Pane prefHeight="75.0" prefWidth="21.0" />
|
||||||
|
<Button mnemonicParsing="false" onAction="#onOpenClicked" text="Open nodes ..." />
|
||||||
|
<Button fx:id="buildButton" mnemonicParsing="false" text="Build">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
<ToggleButton fx:id="localDockerBtn" mnemonicParsing="false" selected="true" text="Local Docker">
|
||||||
|
<toggleGroup>
|
||||||
|
<ToggleGroup fx:id="target" />
|
||||||
|
</toggleGroup>
|
||||||
|
</ToggleButton>
|
||||||
|
<ToggleButton fx:id="azureBtn" mnemonicParsing="false" text="Azure" toggleGroup="$target" />
|
||||||
|
<ChoiceBox fx:id="templateChoiceBox" prefWidth="150.0">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</ChoiceBox>
|
||||||
|
<Button fx:id="addInstanceButton" mnemonicParsing="false" text="Add Instance">
|
||||||
|
<HBox.margin>
|
||||||
|
<Insets bottom="20.0" left="20.0" right="20.0" top="20.0" />
|
||||||
|
</HBox.margin>
|
||||||
|
</Button>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
<HBox maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="461.0" prefWidth="1014.0" VBox.vgrow="ALWAYS">
|
||||||
|
<children>
|
||||||
|
<SplitPane dividerPositions="0.6940371456500489" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" HBox.hgrow="ALWAYS">
|
||||||
|
<items>
|
||||||
|
<TableView fx:id="nodeTableView" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="458.0" prefWidth="662.0" />
|
||||||
|
<TextArea fx:id="infoTextArea" maxHeight="1.7976931348623157E308" maxWidth="1.7976931348623157E308" prefHeight="458.0" prefWidth="353.0" />
|
||||||
|
</items>
|
||||||
|
</SplitPane>
|
||||||
|
</children>
|
||||||
|
</HBox>
|
||||||
|
</children>
|
||||||
|
</VBox>
|
Loading…
x
Reference in New Issue
Block a user