mirror of
https://github.com/corda/corda.git
synced 2024-12-29 09:18:58 +00:00
Change tabs to search field in state machine view. Some carving of UI.
This commit is contained in:
parent
e402f4d5af
commit
608550c920
@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort
|
|||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
import net.corda.client.rpc.CordaRPCClient
|
||||||
import net.corda.client.rpc.CordaRPCClientConfiguration
|
import net.corda.client.rpc.CordaRPCClientConfiguration
|
||||||
import net.corda.core.flows.StateMachineRunId
|
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||||
@ -63,7 +62,9 @@ class NodeMonitorModel {
|
|||||||
Observable.empty<ProgressTrackingEvent>()
|
Observable.empty<ProgressTrackingEvent>()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.subscribe(progressTrackingSubject)
|
|
||||||
|
// We need to retry, because when flow errors, we unsubscribe from progressTrackingSubject. So we end up with stream of state machine updates and no progress trackers.
|
||||||
|
futureProgressTrackerUpdates.startWith(currentProgressTrackerUpdates).flatMap { it }.retry().subscribe(progressTrackingSubject)
|
||||||
|
|
||||||
// Now the state machines
|
// Now the state machines
|
||||||
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
|
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }
|
||||||
|
@ -1,20 +1,18 @@
|
|||||||
package net.corda.client.jfx.model
|
package net.corda.client.jfx.model
|
||||||
|
|
||||||
import javafx.beans.binding.Bindings
|
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import net.corda.client.jfx.utils.LeftOuterJoinedMap
|
import net.corda.client.jfx.utils.LeftOuterJoinedMap
|
||||||
import net.corda.client.jfx.utils.flatten
|
|
||||||
import net.corda.client.jfx.utils.fold
|
import net.corda.client.jfx.utils.fold
|
||||||
import net.corda.client.jfx.utils.getObservableValues
|
import net.corda.client.jfx.utils.getObservableValues
|
||||||
import net.corda.client.jfx.utils.map
|
|
||||||
import net.corda.client.jfx.utils.recordAsAssociation
|
import net.corda.client.jfx.utils.recordAsAssociation
|
||||||
import net.corda.core.ErrorOr
|
import net.corda.core.ErrorOr
|
||||||
import net.corda.core.flows.FlowInitiator
|
import net.corda.core.flows.FlowInitiator
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.messaging.StateMachineInfo
|
import net.corda.core.messaging.StateMachineInfo
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
|
import org.fxmisc.easybind.EasyBind
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
|
|
||||||
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
|
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
|
||||||
@ -28,20 +26,19 @@ data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val mess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
data class ProgressStatus(val status: String)
|
data class ProgressStatus(val status: String?)
|
||||||
|
|
||||||
sealed class StateMachineStatus {
|
sealed class StateMachineStatus {
|
||||||
data class Added(val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus()
|
data class Added(val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus()
|
||||||
data class Removed(val result: ErrorOr<*>) : StateMachineStatus()
|
data class Removed(val result: ErrorOr<*>) : StateMachineStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO StateMachineData and StateMachineInfo
|
|
||||||
data class StateMachineData(
|
data class StateMachineData(
|
||||||
val id: StateMachineRunId,
|
val id: StateMachineRunId,
|
||||||
val stateMachineName: String,
|
val stateMachineName: String,
|
||||||
val flowInitiator: FlowInitiator,
|
val flowInitiator: FlowInitiator,
|
||||||
val addRmStatus: ObservableValue<StateMachineStatus>,
|
val addRmStatus: ObservableValue<StateMachineStatus>,
|
||||||
val stateMachineStatus: ObservableValue<ProgressStatus?>
|
val stateMachineStatus: ObservableValue<ProgressStatus>
|
||||||
)
|
)
|
||||||
|
|
||||||
class StateMachineDataModel {
|
class StateMachineDataModel {
|
||||||
@ -65,22 +62,17 @@ class StateMachineDataModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||||
val smStatus = status.value as StateMachineStatus.Added // TODO not always added
|
val smStatus = status.value as StateMachineStatus.Added
|
||||||
// todo easybind
|
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status,
|
||||||
Bindings.createObjectBinding({
|
EasyBind.map(progress) {
|
||||||
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status, progress.map { it?.let { ProgressStatus(it.message) } })
|
if (it == null) {
|
||||||
}, arrayOf(progress, status))
|
ProgressStatus(null)
|
||||||
}.getObservableValues().flatten()
|
} else {
|
||||||
|
ProgressStatus(it.message)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}.getObservableValues()
|
||||||
|
|
||||||
val stateMachinesInProgress = stateMachineDataList.filtered { it.addRmStatus.value !is StateMachineStatus.Removed }
|
val stateMachinesAll = stateMachineDataList
|
||||||
val stateMachinesDone = stateMachineDataList.filtered { it.addRmStatus.value is StateMachineStatus.Removed }
|
|
||||||
val stateMachinesFinished = stateMachinesDone.filtered {
|
|
||||||
val res = it.addRmStatus.value as StateMachineStatus.Removed
|
|
||||||
res.result.error == null
|
|
||||||
}
|
|
||||||
val stateMachinesError = stateMachinesDone.filtered {
|
|
||||||
val res = it.addRmStatus.value as StateMachineStatus.Removed
|
|
||||||
res.result.error != null
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -54,7 +54,6 @@ data class PartiallyResolvedTransaction(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO Do we want to have mapping tx <-> StateMachine?
|
|
||||||
/**
|
/**
|
||||||
* This model provides an observable list of transactions and what state machines/flows recorded them
|
* This model provides an observable list of transactions and what state machines/flows recorded them
|
||||||
*/
|
*/
|
||||||
|
@ -18,7 +18,7 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
|
|||||||
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
protected val issueRefGenerator = Generator.intRange(0, 1).map { number -> OpaqueBytes(ByteArray(1, { number.toByte() })) }
|
||||||
protected val amountGenerator = Generator.longRange(10000, 1000000)
|
protected val amountGenerator = Generator.longRange(10000, 1000000)
|
||||||
protected val currencyGenerator = Generator.pickOne(currencies)
|
protected val currencyGenerator = Generator.pickOne(currencies)
|
||||||
protected val currencyMap: MutableMap<Currency, Long> = mutableMapOf(USD to 0L, GBP to 0L) // Used for rough estimation of how much money we have in general.
|
protected val currencyMap: MutableMap<Currency, Long> = mutableMapOf(USD to 0L, GBP to 0L) // Used for estimation of how much money we have in general.
|
||||||
|
|
||||||
protected fun addToMap(ccy: Currency, amount: Long) {
|
protected fun addToMap(ccy: Currency, amount: Long) {
|
||||||
val value = currencyMap[ccy]
|
val value = currencyMap[ccy]
|
||||||
@ -47,7 +47,7 @@ open class EventGenerator(val parties: List<Party>, val currencies: List<Currenc
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* [Generator]s for incoming/outgoing events of starting different [Cash] flows. It invokes flows that throw exceptions
|
* [Generator]s for incoming/outgoing events of starting different cash flows. It invokes flows that throw exceptions
|
||||||
* for use in explorer flow triage. Exceptions are of kind spending/exiting too much cash.
|
* for use in explorer flow triage. Exceptions are of kind spending/exiting too much cash.
|
||||||
*/
|
*/
|
||||||
class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party): EventGenerator(parties, currencies, notary) {
|
class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party): EventGenerator(parties, currencies, notary) {
|
||||||
|
@ -217,7 +217,7 @@ abstract class FlowLogic<out T> {
|
|||||||
fun track(): Pair<String, Observable<String>>? {
|
fun track(): Pair<String, Observable<String>>? {
|
||||||
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
|
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
|
||||||
return progressTracker?.let {
|
return progressTracker?.let {
|
||||||
it.currentStep.toString() to it.changes.map { it.toString() }
|
it.currentStep.label to it.changes.map { it.toString() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,14 +5,13 @@ import net.corda.client.mock.ErrorFlowsEventGenerator
|
|||||||
import net.corda.client.mock.EventGenerator
|
import net.corda.client.mock.EventGenerator
|
||||||
import net.corda.client.mock.Generator
|
import net.corda.client.mock.Generator
|
||||||
import net.corda.client.mock.pickOne
|
import net.corda.client.mock.pickOne
|
||||||
import net.corda.client.rpc.CordaRPCClient
|
|
||||||
import net.corda.client.rpc.CordaRPCConnection
|
import net.corda.client.rpc.CordaRPCConnection
|
||||||
import net.corda.contracts.asset.Cash
|
import net.corda.contracts.asset.Cash
|
||||||
import net.corda.core.contracts.Amount
|
import net.corda.core.contracts.Amount
|
||||||
import net.corda.core.contracts.GBP
|
import net.corda.core.contracts.GBP
|
||||||
import net.corda.core.contracts.USD
|
import net.corda.core.contracts.USD
|
||||||
import net.corda.core.crypto.Party
|
|
||||||
import net.corda.core.failure
|
import net.corda.core.failure
|
||||||
|
import net.corda.core.identity.Party
|
||||||
import net.corda.core.messaging.CordaRPCOps
|
import net.corda.core.messaging.CordaRPCOps
|
||||||
import net.corda.core.messaging.FlowHandle
|
import net.corda.core.messaging.FlowHandle
|
||||||
import net.corda.core.node.services.ServiceInfo
|
import net.corda.core.node.services.ServiceInfo
|
||||||
@ -154,7 +153,7 @@ class ExplorerSimulation(val options: OptionSet) {
|
|||||||
is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let {
|
is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let {
|
||||||
println("${Instant.now()} [$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
|
println("${Instant.now()} [$i] ISSUING ${command.amount} with ref ${command.issueRef} to ${command.recipient}")
|
||||||
command.startFlow(it).log(i, "${command.amount.token}Issuer")
|
command.startFlow(it).log(i, "${command.amount.token}Issuer")
|
||||||
} ?: command.startFlow(issuers[USD]!!).log(i, "${command.amount.token}Issuer") // TODO workaround
|
}
|
||||||
is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let {
|
is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let {
|
||||||
println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}")
|
println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}")
|
||||||
command.startFlow(it).log(i, "${command.amount.token}Exit")
|
command.startFlow(it).log(i, "${command.amount.token}Exit")
|
||||||
|
@ -5,9 +5,9 @@ import net.corda.core.flows.FlowInitiator
|
|||||||
object FlowInitiatorFormatter : Formatter<FlowInitiator> {
|
object FlowInitiatorFormatter : Formatter<FlowInitiator> {
|
||||||
override fun format(value: FlowInitiator): String {
|
override fun format(value: FlowInitiator): String {
|
||||||
return when (value) {
|
return when (value) {
|
||||||
is FlowInitiator.Scheduled -> "Started by scheduled state:: " + value.scheduledState.ref.toString() // TODO format that
|
is FlowInitiator.Scheduled -> "Started by scheduled state:: " + value.scheduledState.ref.toString() // TODO How do we want to format that?
|
||||||
is FlowInitiator.Shell -> "Started via shell"
|
is FlowInitiator.Shell -> "Started via shell"
|
||||||
is FlowInitiator.Peer -> "Peer legal name: " + value.party.name //TODO format that
|
is FlowInitiator.Peer -> "Peer legal name: " + PartyNameFormatter.short.format(value.party.name)
|
||||||
is FlowInitiator.RPC -> "Rpc username: " + value.username
|
is FlowInitiator.RPC -> "Rpc username: " + value.username
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -198,8 +198,8 @@ fun identicon(secureHash: SecureHash, size: Double): ImageView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun identiconToolTip(secureHash: SecureHash): Tooltip {
|
fun identiconToolTip(secureHash: SecureHash, description: String? = null): Tooltip {
|
||||||
return Tooltip(Splitter.fixedLength(16).split("$secureHash").joinToString("\n")).apply {
|
return Tooltip(Splitter.fixedLength(16).split("${description ?: secureHash}").joinToString("\n")).apply {
|
||||||
contentDisplay = ContentDisplay.TOP
|
contentDisplay = ContentDisplay.TOP
|
||||||
textAlignment = TextAlignment.CENTER
|
textAlignment = TextAlignment.CENTER
|
||||||
graphic = identicon(secureHash, 90.0)
|
graphic = identicon(secureHash, 90.0)
|
||||||
|
@ -23,7 +23,8 @@ import tornadofx.*
|
|||||||
* TODO : Predictive text?
|
* TODO : Predictive text?
|
||||||
* TODO : Regex?
|
* TODO : Regex?
|
||||||
*/
|
*/
|
||||||
class SearchField<T>(private val data: ObservableList<T>, vararg filterCriteria: Pair<String, (T, String) -> Boolean>) : UIComponent() {
|
class SearchField<T>(private val data: ObservableList<T>, val filterCriteria: List<Pair<String, (T, String) -> Boolean>>,
|
||||||
|
val disabledFields: List<String> = emptyList()) : UIComponent() {
|
||||||
override val root: Parent by fxml()
|
override val root: Parent by fxml()
|
||||||
private val textField by fxid<TextField>()
|
private val textField by fxid<TextField>()
|
||||||
private val clearButton by fxid<Node>()
|
private val clearButton by fxid<Node>()
|
||||||
@ -34,7 +35,7 @@ class SearchField<T>(private val data: ObservableList<T>, vararg filterCriteria:
|
|||||||
val text = textField.text
|
val text = textField.text
|
||||||
val category = searchCategory.value
|
val category = searchCategory.value
|
||||||
data.filtered { data ->
|
data.filtered { data ->
|
||||||
text.isNullOrBlank() || if (category == ALL) {
|
(text.isNullOrBlank() && textField.isVisible) || if (category == ALL) {
|
||||||
filterCriteria.any { it.second(data, text) }
|
filterCriteria.any { it.second(data, text) }
|
||||||
} else {
|
} else {
|
||||||
filterCriteria.toMap()[category]?.invoke(data, text) ?: false
|
filterCriteria.toMap()[category]?.invoke(data, text) ?: false
|
||||||
@ -73,5 +74,7 @@ class SearchField<T>(private val data: ObservableList<T>, vararg filterCriteria:
|
|||||||
}
|
}
|
||||||
"Filter by $category."
|
"Filter by $category."
|
||||||
})
|
})
|
||||||
|
textField.visibleProperty().bind(searchCategory.valueProperty().map { it !in disabledFields })
|
||||||
|
// TODO Maybe it will be better to replace these categories with comboBox? For example Result with choice: succes, in progress, error.
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -5,14 +5,10 @@ import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
|||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import javafx.geometry.HPos
|
import javafx.geometry.HPos
|
||||||
import javafx.geometry.Insets
|
|
||||||
import javafx.geometry.Pos
|
import javafx.geometry.Pos
|
||||||
import javafx.scene.Parent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.control.ScrollPane
|
|
||||||
import javafx.scene.control.TabPane
|
|
||||||
import javafx.scene.control.TableView
|
import javafx.scene.control.TableView
|
||||||
import javafx.scene.control.TitledPane
|
|
||||||
import javafx.scene.layout.BorderPane
|
import javafx.scene.layout.BorderPane
|
||||||
import javafx.scene.layout.GridPane
|
import javafx.scene.layout.GridPane
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.layout.VBox
|
||||||
@ -37,33 +33,29 @@ import net.corda.explorer.model.CordaWidget
|
|||||||
import net.corda.explorer.ui.setCustomCellFactory
|
import net.corda.explorer.ui.setCustomCellFactory
|
||||||
import tornadofx.*
|
import tornadofx.*
|
||||||
|
|
||||||
// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows and other communication)
|
// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows).
|
||||||
class StateMachineViewer : CordaView("Flow Triage") {
|
class StateMachineViewer : CordaView("Flow Triage") {
|
||||||
override val root by fxml<TabPane>()
|
override val root by fxml<BorderPane>()
|
||||||
override val icon = FontAwesomeIcon.HEARTBEAT
|
override val icon = FontAwesomeIcon.HEARTBEAT
|
||||||
override val widgets = listOf(CordaWidget(title, StateMachineViewer.StateMachineWidget())).observable()
|
override val widgets = listOf(CordaWidget(title, StateMachineViewer.StateMachineWidget())).observable()
|
||||||
private val progressViewTable by fxid<TableView<StateMachineData>>()
|
private val allViewTable by fxid<TableView<StateMachineData>>()
|
||||||
private val doneViewTable by fxid<TableView<StateMachineData>>()
|
private val matchingFlowsLabel by fxid<Label>()
|
||||||
private val errorViewTable by fxid<TableView<StateMachineData>>()
|
|
||||||
|
private val stateMachinesAll by observableList(StateMachineDataModel::stateMachinesAll)
|
||||||
|
|
||||||
private class StateMachineWidget : BorderPane() {
|
private class StateMachineWidget : BorderPane() {
|
||||||
private val flows by observableListReadOnly(StateMachineDataModel::stateMachinesInProgress)
|
private val stateMachinesAll by observableListReadOnly(StateMachineDataModel::stateMachinesAll)
|
||||||
|
|
||||||
// TODO can add stats: in progress, errored, maybe done to the widget?
|
|
||||||
init {
|
init {
|
||||||
right {
|
right {
|
||||||
label {
|
label {
|
||||||
textProperty().bind(Bindings.size(flows).map(Number::toString))
|
textProperty().bind(Bindings.size(stateMachinesAll).map(Number::toString))
|
||||||
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
|
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private val stateMachinesInProgress by observableList(StateMachineDataModel::stateMachinesInProgress)
|
|
||||||
private val stateMachinesFinished by observableList(StateMachineDataModel::stateMachinesFinished)
|
|
||||||
private val stateMachinesError by observableList(StateMachineDataModel::stateMachinesError)
|
|
||||||
|
|
||||||
fun makeColumns(table: TableView<StateMachineData>, tableItems: ObservableList<StateMachineData>, withResult: Boolean = true) {
|
fun makeColumns(table: TableView<StateMachineData>, tableItems: ObservableList<StateMachineData>, withResult: Boolean = true) {
|
||||||
table.apply {
|
table.apply {
|
||||||
items = tableItems
|
items = tableItems
|
||||||
@ -76,23 +68,21 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
maxWidth = 26.0
|
maxWidth = 26.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
column("ID", StateMachineData::id) { // TODO kill that ID column
|
// TODO Kill that ID column or replace it with something useful when we will have flow audit utilities.
|
||||||
|
// For now it's rather for visual purpose, so you can observe flow.
|
||||||
|
column("ID", StateMachineData::id) {
|
||||||
minWidth = 100.0
|
minWidth = 100.0
|
||||||
maxWidth = 200.0
|
maxWidth = 200.0
|
||||||
}.setCustomCellFactory {
|
}.setCustomCellFactory {
|
||||||
label("$it") {
|
label("$it") {
|
||||||
val hash = SecureHash.sha256(it.toString())
|
val hash = SecureHash.sha256(it.toString())
|
||||||
graphic = identicon(hash, 15.0)
|
graphic = identicon(hash, 15.0)
|
||||||
tooltip = identiconToolTip(hash) //TODO Have id instead of hash.
|
tooltip = identiconToolTip(hash, it.toString())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
column("Flow name", StateMachineData::stateMachineName).cellFormat { text = FlowNameFormatter.boring.format(it) }
|
column("Flow name", StateMachineData::stateMachineName).cellFormat { text = FlowNameFormatter.boring.format(it) }
|
||||||
column("Initiator", StateMachineData::flowInitiator).cellFormat { text = FlowInitiatorFormatter.format(it) }
|
column("Initiator", StateMachineData::flowInitiator).cellFormat { text = FlowInitiatorFormatter.format(it) }
|
||||||
column("Flow Status", StateMachineData::stateMachineStatus).cellFormat {
|
column("Flow Status", StateMachineData::stateMachineStatus).cellFormat { text = it.status ?: "No progress data" }
|
||||||
if (it == null)
|
|
||||||
text = "No progress data"
|
|
||||||
else text = it.status
|
|
||||||
} // TODO null
|
|
||||||
column("Result", StateMachineData::addRmStatus).setCustomCellFactory {
|
column("Result", StateMachineData::addRmStatus).setCustomCellFactory {
|
||||||
if (it is StateMachineStatus.Removed) {
|
if (it is StateMachineStatus.Removed) {
|
||||||
if (it.result.error == null) {
|
if (it.result.error == null) {
|
||||||
@ -115,8 +105,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
label("In progress") {
|
label("In progress") {
|
||||||
// TODO Other icons: spnner, hourglass-half, hourglass-1, send-o, space-shuttle ;)
|
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply { // Blazing fast! Try not to blink.
|
||||||
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply {
|
|
||||||
glyphSize = 15.0
|
glyphSize = 15.0
|
||||||
textAlignment = TextAlignment.CENTER
|
textAlignment = TextAlignment.CENTER
|
||||||
style = "-fx-fill: lightslategrey"
|
style = "-fx-fill: lightslategrey"
|
||||||
@ -128,15 +117,33 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init {
|
init {
|
||||||
makeColumns(progressViewTable, stateMachinesInProgress, false)
|
val searchField = SearchField(stateMachinesAll, listOf(
|
||||||
makeColumns(doneViewTable, stateMachinesFinished)
|
"Flow name" to { sm, s -> sm.stateMachineName.contains(s, true) },
|
||||||
makeColumns(errorViewTable, stateMachinesError)
|
"Initiator" to { sm, s -> FlowInitiatorFormatter.format(sm.flowInitiator).contains(s, true) },
|
||||||
|
"Flow Status" to { sm, s -> val stat = sm.stateMachineStatus.value.status ?: "No progress data"
|
||||||
|
stat.contains(s, true) },
|
||||||
|
"Error" to { sm, _ -> val smAddRm = sm.addRmStatus.value
|
||||||
|
if (smAddRm is StateMachineStatus.Removed)
|
||||||
|
smAddRm.result.error != null
|
||||||
|
else false },
|
||||||
|
"Done" to { sm, _ -> val smAddRm = sm.addRmStatus.value
|
||||||
|
if (smAddRm is StateMachineStatus.Removed)
|
||||||
|
smAddRm.result.error == null
|
||||||
|
else false },
|
||||||
|
"In progress" to { sm, _ -> sm.addRmStatus.value !is StateMachineStatus.Removed }),
|
||||||
|
listOf("Error", "Done", "In progress")
|
||||||
|
)
|
||||||
|
root.top = searchField.root
|
||||||
|
makeColumns(allViewTable, searchField.filteredData)
|
||||||
|
matchingFlowsLabel.textProperty().bind(Bindings.size(allViewTable.items).map {
|
||||||
|
"$it matching flow${if (it == 1) "" else "s"}"
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class StateMachineDetailsView(val smmData: StateMachineData) : Fragment() {
|
private inner class StateMachineDetailsView(val smmData: StateMachineData) : Fragment() {
|
||||||
override val root by fxml<Parent>()
|
override val root by fxml<Parent>()
|
||||||
private val flowNameLabel by fxid<Label>()
|
private val flowNameLabel by fxid<Label>()
|
||||||
private val flowProgressPane by fxid<TitledPane>()
|
private val flowProgressVBox by fxid<VBox>()
|
||||||
private val flowInitiatorGrid by fxid<GridPane>()
|
private val flowInitiatorGrid by fxid<GridPane>()
|
||||||
private val flowResultVBox by fxid<VBox>()
|
private val flowResultVBox by fxid<VBox>()
|
||||||
|
|
||||||
@ -144,9 +151,10 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
flowNameLabel.apply {
|
flowNameLabel.apply {
|
||||||
text = FlowNameFormatter.boring.format(smmData.stateMachineName)
|
text = FlowNameFormatter.boring.format(smmData.stateMachineName)
|
||||||
}
|
}
|
||||||
flowProgressPane.apply {
|
//TODO It would be nice to have flow graph with showing progress steps with subflows + timestamps (left it for second iteration).
|
||||||
content = label {
|
flowProgressVBox.apply {
|
||||||
text = smmData.stateMachineStatus.value?.status // TODO later we can do some magic with showing progress steps with subflows
|
label {
|
||||||
|
text = smmData.stateMachineStatus.value?.status ?: "No progress data"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
when (smmData.flowInitiator) {
|
when (smmData.flowInitiator) {
|
||||||
@ -164,27 +172,27 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
|
|
||||||
private fun <T>makeResultVBox(vbox: VBox, result: T) {
|
private fun <T>makeResultVBox(vbox: VBox, result: T) {
|
||||||
if (result == null) {
|
if (result == null) {
|
||||||
vbox.apply { label("No return value from flow.") }
|
vbox.apply { label("No return value from flow.").apply { style { fontWeight = FontWeight.BOLD } } }
|
||||||
} else if (result is SignedTransaction) {
|
} else if (result is SignedTransaction) {
|
||||||
// scrollpane {
|
|
||||||
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
|
||||||
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
|
||||||
// TODO Make link to transaction view
|
// TODO Make link to transaction view
|
||||||
vbox.apply {
|
vbox.apply {
|
||||||
label("Signed transaction")
|
label("Signed transaction").apply { style { fontWeight = FontWeight.BOLD } }
|
||||||
label {
|
label {
|
||||||
text = result.id.toString()
|
text = result.id.toString()
|
||||||
graphic = identicon(result.id, 30.0)
|
graphic = identicon(result.id, 30.0)
|
||||||
tooltip = identiconToolTip(result.id)
|
tooltip = identiconToolTip(result.id)
|
||||||
}
|
}
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
} else if (result is Unit) {
|
} else if (result is Unit) {
|
||||||
vbox.apply { label("Flow completed with success.") }
|
vbox.apply { label("Flow completed with success.").apply { style { fontWeight = FontWeight.BOLD } } }
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
// TODO Here we could have sth different than SignedTransaction
|
// TODO Here we could have sth different than SignedTransaction/Unit
|
||||||
vbox.apply { label(result.toString()) }
|
vbox.apply {
|
||||||
|
label("Flow completed with success. Result: ").apply { style { fontWeight = FontWeight.BOLD } }
|
||||||
|
label(result.toString())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -198,21 +206,15 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// TODO border styling?
|
|
||||||
vbox.apply {
|
vbox.apply {
|
||||||
vbox {
|
vbox {
|
||||||
spacing = 10.0
|
spacing = 10.0
|
||||||
label { text = error::class.simpleName }
|
label { text = error::class.simpleName }
|
||||||
scrollpane { //TODO do that error scroll pane nicely
|
|
||||||
hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
|
||||||
vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
|
||||||
label { text = error.message }
|
label { text = error.message }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
// TODO test
|
|
||||||
private fun makeShellGrid(gridPane: GridPane) {
|
private fun makeShellGrid(gridPane: GridPane) {
|
||||||
val title = gridPane.lookup("#flowInitiatorTitle") as Label
|
val title = gridPane.lookup("#flowInitiatorTitle") as Label
|
||||||
title.apply {
|
title.apply {
|
||||||
@ -226,9 +228,6 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
text = "Flow started by a peer node"
|
text = "Flow started by a peer node"
|
||||||
}
|
}
|
||||||
gridPane.apply{
|
gridPane.apply{
|
||||||
// scrollpane { // TODO scrollbar vbox + hbox
|
|
||||||
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
|
||||||
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
|
||||||
row {
|
row {
|
||||||
label("Legal name: ") {
|
label("Legal name: ") {
|
||||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||||
@ -236,7 +235,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
minWidth = 150.0
|
minWidth = 150.0
|
||||||
prefWidth = 150.0
|
prefWidth = 150.0
|
||||||
}
|
}
|
||||||
label(initiator.party.name) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
label(initiator.party.name.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||||
}
|
}
|
||||||
row {
|
row {
|
||||||
label("Owning key: ") {
|
label("Owning key: ") {
|
||||||
@ -269,7 +268,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
|
|
||||||
// TODO test
|
// TODO test
|
||||||
private fun makeScheduledGrid(gridPane: GridPane, initiator: FlowInitiator.Scheduled) {
|
private fun makeScheduledGrid(gridPane: GridPane, initiator: FlowInitiator.Scheduled) {
|
||||||
val title = gridPane.lookup("flowInitiatorTitle") as Label
|
val title = gridPane.lookup("#flowInitiatorTitle") as Label
|
||||||
title.apply {
|
title.apply {
|
||||||
text = "Flow started as scheduled activity"
|
text = "Flow started as scheduled activity"
|
||||||
}
|
}
|
||||||
@ -280,7 +279,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
style { fontWeight = FontWeight.BOLD }
|
style { fontWeight = FontWeight.BOLD }
|
||||||
prefWidth = 150.0
|
prefWidth = 150.0
|
||||||
}
|
}
|
||||||
label(initiator.scheduledState.ref.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
|
label(initiator.scheduledState.ref.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||||
}
|
}
|
||||||
row {
|
row {
|
||||||
label("Scheduled at: ") {
|
label("Scheduled at: ") {
|
||||||
@ -288,7 +287,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
|
|||||||
style { fontWeight = FontWeight.BOLD }
|
style { fontWeight = FontWeight.BOLD }
|
||||||
prefWidth = 150.0
|
prefWidth = 150.0
|
||||||
}
|
}
|
||||||
label(initiator.scheduledState.scheduledAt.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
|
label(initiator.scheduledState.scheduledAt.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -105,13 +105,13 @@ class TransactionViewer : CordaView("Transactions") {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val searchField = SearchField(transactions,
|
val searchField = SearchField(transactions, listOf(
|
||||||
"Transaction ID" to { tx, s -> "${tx.id}".contains(s, true) },
|
"Transaction ID" to { tx, s -> "${tx.id}".contains(s, true) },
|
||||||
"Input" to { tx, s -> tx.inputs.resolved.any { it.state.data.contract.javaClass.simpleName.contains(s, true) } },
|
"Input" to { tx, s -> tx.inputs.resolved.any { it.state.data.contract.javaClass.simpleName.contains(s, true) } },
|
||||||
"Output" to { tx, s -> tx.outputs.any { it.state.data.contract.javaClass.simpleName.contains(s, true) } },
|
"Output" to { tx, s -> tx.outputs.any { it.state.data.contract.javaClass.simpleName.contains(s, true) } },
|
||||||
"Input Party" to { tx, s -> tx.inputParties.any { it.any { it.value?.legalIdentity?.name?.commonName?.contains(s, true) ?: false } } },
|
"Input Party" to { tx, s -> tx.inputParties.any { it.any { it.value?.legalIdentity?.name?.commonName?.contains(s, true) ?: false } } },
|
||||||
"Output Party" to { tx, s -> tx.outputParties.any { it.any { it.value?.legalIdentity?.name?.commonName?.contains(s, true) ?: false } } },
|
"Output Party" to { tx, s -> tx.outputParties.any { it.any { it.value?.legalIdentity?.name?.commonName?.contains(s, true) ?: false } } },
|
||||||
"Command Type" to { tx, s -> tx.commandTypes.any { it.simpleName.contains(s, true) } }
|
"Command Type" to { tx, s -> tx.commandTypes.any { it.simpleName.contains(s, true) } })
|
||||||
)
|
)
|
||||||
root.top = searchField.root
|
root.top = searchField.root
|
||||||
// Transaction table
|
// Transaction table
|
||||||
@ -163,6 +163,7 @@ class TransactionViewer : CordaView("Transactions") {
|
|||||||
prefHeight = 400.0
|
prefHeight = 400.0
|
||||||
}.apply {
|
}.apply {
|
||||||
// Column stays the same size, but we don't violate column restricted resize policy for the whole table view.
|
// Column stays the same size, but we don't violate column restricted resize policy for the whole table view.
|
||||||
|
// It removes that irritating column at the end of table that does nothing.
|
||||||
minWidth = 26.0
|
minWidth = 26.0
|
||||||
maxWidth = 26.0
|
maxWidth = 26.0
|
||||||
}
|
}
|
||||||
|
@ -146,9 +146,9 @@ class CashViewer : CordaView("Cash") {
|
|||||||
* one which produces more results, which seems to work, as the set of currency strings don't really overlap with
|
* one which produces more results, which seems to work, as the set of currency strings don't really overlap with
|
||||||
* issuer strings.
|
* issuer strings.
|
||||||
*/
|
*/
|
||||||
val searchField = SearchField(cashStates,
|
val searchField = SearchField(cashStates, listOf(
|
||||||
"Currency" to { state, text -> state.state.data.amount.token.product.toString().contains(text, true) },
|
"Currency" to { state, text -> state.state.data.amount.token.product.toString().contains(text, true) },
|
||||||
"Issuer" to { state, text -> state.resolveIssuer().value?.name?.commonName?.contains(text, true) ?: false }
|
"Issuer" to { state, text -> state.resolveIssuer().value?.name?.commonName?.contains(text, true) ?: false })
|
||||||
)
|
)
|
||||||
root.top = hbox(5.0) {
|
root.top = hbox(5.0) {
|
||||||
button("New Transaction", FontAwesomeIconView(FontAwesomeIcon.PLUS)) {
|
button("New Transaction", FontAwesomeIconView(FontAwesomeIcon.PLUS)) {
|
||||||
|
@ -270,3 +270,10 @@
|
|||||||
.connection-bank-to-regulator {
|
.connection-bank-to-regulator {
|
||||||
-fx-stroke: red;
|
-fx-stroke: red;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Flow details */
|
||||||
|
.smm-detail-grid {
|
||||||
|
-fx-border-color: -color-3;
|
||||||
|
-fx-border-width: 1;
|
||||||
|
-fx-border-radius: 2;
|
||||||
|
}
|
||||||
|
@ -2,39 +2,46 @@
|
|||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.ScrollPane?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
<?import javafx.scene.control.TitledPane?>
|
||||||
<?import javafx.scene.layout.GridPane?>
|
|
||||||
<?import javafx.scene.layout.ColumnConstraints?>
|
<?import javafx.scene.layout.ColumnConstraints?>
|
||||||
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.RowConstraints?>
|
<?import javafx.scene.layout.RowConstraints?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
|
||||||
<GridPane stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
<GridPane styleClass="smm-detail-grid" stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5" left="5" right="5" top="5" />
|
<Insets bottom="5" left="5" right="5" top="5" />
|
||||||
</padding>
|
</padding>
|
||||||
<TitledPane fx:id="flowNamePane" collapsible="false" text="Flow name" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.rowIndex="0">
|
<TitledPane fx:id="flowNamePane" collapsible="false" text="Flow name" GridPane.columnSpan="2" GridPane.fillWidth="true" GridPane.rowIndex="0">
|
||||||
<!--TODO text styling-->
|
<Label fx:id="flowNameLabel" style="-fx-font-weight: bold"/>
|
||||||
<Label fx:id="flowNameLabel"/>
|
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator"
|
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
|
||||||
GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
|
<ScrollPane maxHeight="Infinity" minHeight="120">
|
||||||
<GridPane fx:id="flowInitiatorGrid" vgap="10.0" hgap="10.0">
|
<GridPane fx:id="flowInitiatorGrid" hgap="10.0" vgap="10.0">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5.0" left="5.0" right="10.0" top="10.0" />
|
<Insets bottom="5.0" left="5.0" right="10.0" top="10.0" />
|
||||||
</padding>
|
</padding>
|
||||||
<!--TODO text styling-->
|
<Label fx:id="flowInitiatorTitle" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="LEFT" GridPane.rowIndex="0" style="-fx-font-weight: bold"/>
|
||||||
<Label fx:id="flowInitiatorTitle" GridPane.columnSpan="2" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.halignment="LEFT"/>
|
|
||||||
</GridPane>
|
</GridPane>
|
||||||
|
</ScrollPane>
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane fx:id="flowResultPane" collapsible="false" maxHeight="Infinity" text="Result" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
<TitledPane fx:id="flowResultPane" collapsible="false" maxHeight="Infinity" text="Result" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||||
|
<ScrollPane maxHeight="Infinity">
|
||||||
<VBox fx:id="flowResultVBox" spacing="10.0">
|
<VBox fx:id="flowResultVBox" spacing="10.0">
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5" left="5" right="5" top="5" />
|
<Insets bottom="5" left="5" right="5" top="5" />
|
||||||
</padding>
|
</padding>
|
||||||
</VBox>
|
</VBox>
|
||||||
|
</ScrollPane>
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<TitledPane fx:id="flowProgressPane" collapsible="false" maxWidth="Infinity" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
|
<TitledPane fx:id="flowProgressPane" collapsible="false" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
|
||||||
<!--TODO add progress graph-->
|
<!--TODO add progress graph-->
|
||||||
|
<VBox fx:id="flowProgressVBox" spacing="10.0">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5" left="5" right="5" top="5" />
|
||||||
|
</padding>
|
||||||
|
</VBox>
|
||||||
</TitledPane>
|
</TitledPane>
|
||||||
<columnConstraints>
|
<columnConstraints>
|
||||||
<ColumnConstraints minWidth="450.0" />
|
<ColumnConstraints minWidth="450.0" />
|
||||||
|
@ -3,36 +3,22 @@
|
|||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.TableView?>
|
<?import javafx.scene.control.TableView?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
|
||||||
<?import javafx.scene.control.TabPane?>
|
<BorderPane stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.76-ea"
|
||||||
<?import javafx.scene.control.Tab?>
|
xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<TabPane stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1"
|
|
||||||
tabClosingPolicy="UNAVAILABLE">
|
|
||||||
<padding>
|
<padding>
|
||||||
<Insets bottom="5" left="5" right="5" top="5"/>
|
<Insets top="5" left="5" right="5" bottom="5"/>
|
||||||
</padding>
|
</padding>
|
||||||
<Tab text="In progress" fx:id="progressFlowsTab">
|
<center>
|
||||||
<TableView fx:id="progressViewTable" VBox.vgrow="ALWAYS">
|
<TableView fx:id="allViewTable" VBox.vgrow="ALWAYS">
|
||||||
<columnResizePolicy>
|
<columnResizePolicy>
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||||
</columnResizePolicy>
|
</columnResizePolicy>
|
||||||
</TableView>
|
</TableView>
|
||||||
</Tab>
|
</center>
|
||||||
<Tab text="Finished" fx:id="doneFlowsTab">
|
<bottom>
|
||||||
<TableView fx:id="doneViewTable" VBox.vgrow="ALWAYS">
|
<Label fx:id="matchingFlowsLabel" text="matching flows(s)"/>
|
||||||
<columnResizePolicy>
|
</bottom>
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
</BorderPane>
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
</Tab>
|
|
||||||
<Tab text="With errors" fx:id="errorFlowsTab">
|
|
||||||
<TableView fx:id="errorViewTable" VBox.vgrow="ALWAYS">
|
|
||||||
<columnResizePolicy>
|
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
</Tab>
|
|
||||||
<!--<bottom>-->
|
|
||||||
<!--<Label fx:id="matchingFlowsLabel" text="matching flow(s)" />-->
|
|
||||||
<!--</bottom>-->
|
|
||||||
</TabPane>
|
|
||||||
|
Loading…
Reference in New Issue
Block a user