Change tabs to search field in state machine view. Some carving of UI.

This commit is contained in:
Katarzyna Streich 2017-05-11 19:04:17 +01:00
parent e402f4d5af
commit 608550c920
15 changed files with 141 additions and 147 deletions

View File

@ -4,7 +4,6 @@ import com.google.common.net.HostAndPort
import javafx.beans.property.SimpleObjectProperty
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCClientConfiguration
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.StateMachineUpdate
import net.corda.core.node.services.NetworkMapCache.MapChange
@ -63,7 +62,9 @@ class NodeMonitorModel {
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
val currentStateMachines = stateMachines.map { StateMachineUpdate.Added(it) }

View File

@ -1,20 +1,18 @@
package net.corda.client.jfx.model
import javafx.beans.binding.Bindings
import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections
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.getObservableValues
import net.corda.client.jfx.utils.map
import net.corda.client.jfx.utils.recordAsAssociation
import net.corda.core.ErrorOr
import net.corda.core.flows.FlowInitiator
import net.corda.core.flows.StateMachineRunId
import net.corda.core.messaging.StateMachineInfo
import net.corda.core.messaging.StateMachineUpdate
import org.fxmisc.easybind.EasyBind
import rx.Observable
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 {
data class Added(val stateMachineName: String, val flowInitiator: FlowInitiator) : StateMachineStatus()
data class Removed(val result: ErrorOr<*>) : StateMachineStatus()
}
// TODO StateMachineData and StateMachineInfo
data class StateMachineData(
val id: StateMachineRunId,
val stateMachineName: String,
val flowInitiator: FlowInitiator,
val addRmStatus: ObservableValue<StateMachineStatus>,
val stateMachineStatus: ObservableValue<ProgressStatus?>
val stateMachineStatus: ObservableValue<ProgressStatus>
)
class StateMachineDataModel {
@ -65,22 +62,17 @@ class StateMachineDataModel {
}
}
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
val smStatus = status.value as StateMachineStatus.Added // TODO not always added
// todo easybind
Bindings.createObjectBinding({
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status, progress.map { it?.let { ProgressStatus(it.message) } })
}, arrayOf(progress, status))
}.getObservableValues().flatten()
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
val smStatus = status.value as StateMachineStatus.Added
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status,
EasyBind.map(progress) {
if (it == null) {
ProgressStatus(null)
} else {
ProgressStatus(it.message)
}
})
}.getObservableValues()
val stateMachinesInProgress = stateMachineDataList.filtered { it.addRmStatus.value !is StateMachineStatus.Removed }
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
}
val stateMachinesAll = stateMachineDataList
}

View File

@ -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
*/

View File

@ -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 amountGenerator = Generator.longRange(10000, 1000000)
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) {
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.
*/
class ErrorFlowsEventGenerator(parties: List<Party>, currencies: List<Currency>, notary: Party): EventGenerator(parties, currencies, notary) {

View File

@ -217,7 +217,7 @@ abstract class FlowLogic<out T> {
fun track(): Pair<String, Observable<String>>? {
// TODO this is not threadsafe, needs an atomic get-step-and-subscribe
return progressTracker?.let {
it.currentStep.toString() to it.changes.map { it.toString() }
it.currentStep.label to it.changes.map { it.toString() }
}
}

View File

@ -5,14 +5,13 @@ import net.corda.client.mock.ErrorFlowsEventGenerator
import net.corda.client.mock.EventGenerator
import net.corda.client.mock.Generator
import net.corda.client.mock.pickOne
import net.corda.client.rpc.CordaRPCClient
import net.corda.client.rpc.CordaRPCConnection
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.GBP
import net.corda.core.contracts.USD
import net.corda.core.crypto.Party
import net.corda.core.failure
import net.corda.core.identity.Party
import net.corda.core.messaging.CordaRPCOps
import net.corda.core.messaging.FlowHandle
import net.corda.core.node.services.ServiceInfo
@ -154,7 +153,7 @@ class ExplorerSimulation(val options: OptionSet) {
is CashFlowCommand.IssueCash -> issuers[command.amount.token]?.let {
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(issuers[USD]!!).log(i, "${command.amount.token}Issuer") // TODO workaround
}
is CashFlowCommand.ExitCash -> issuers[command.amount.token]?.let {
println("${Instant.now()} [$i] EXITING ${command.amount} with ref ${command.issueRef}")
command.startFlow(it).log(i, "${command.amount.token}Exit")

View File

@ -5,9 +5,9 @@ import net.corda.core.flows.FlowInitiator
object FlowInitiatorFormatter : Formatter<FlowInitiator> {
override fun format(value: FlowInitiator): String {
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.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
}
}

View File

@ -198,8 +198,8 @@ fun identicon(secureHash: SecureHash, size: Double): ImageView {
}
}
fun identiconToolTip(secureHash: SecureHash): Tooltip {
return Tooltip(Splitter.fixedLength(16).split("$secureHash").joinToString("\n")).apply {
fun identiconToolTip(secureHash: SecureHash, description: String? = null): Tooltip {
return Tooltip(Splitter.fixedLength(16).split("${description ?: secureHash}").joinToString("\n")).apply {
contentDisplay = ContentDisplay.TOP
textAlignment = TextAlignment.CENTER
graphic = identicon(secureHash, 90.0)

View File

@ -23,7 +23,8 @@ import tornadofx.*
* TODO : Predictive text?
* 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()
private val textField by fxid<TextField>()
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 category = searchCategory.value
data.filtered { data ->
text.isNullOrBlank() || if (category == ALL) {
(text.isNullOrBlank() && textField.isVisible) || if (category == ALL) {
filterCriteria.any { it.second(data, text) }
} else {
filterCriteria.toMap()[category]?.invoke(data, text) ?: false
@ -73,5 +74,7 @@ class SearchField<T>(private val data: ObservableList<T>, vararg filterCriteria:
}
"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.
}
}

View File

@ -5,14 +5,10 @@ import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.beans.binding.Bindings
import javafx.collections.ObservableList
import javafx.geometry.HPos
import javafx.geometry.Insets
import javafx.geometry.Pos
import javafx.scene.Parent
import javafx.scene.control.Label
import javafx.scene.control.ScrollPane
import javafx.scene.control.TabPane
import javafx.scene.control.TableView
import javafx.scene.control.TitledPane
import javafx.scene.layout.BorderPane
import javafx.scene.layout.GridPane
import javafx.scene.layout.VBox
@ -37,33 +33,29 @@ import net.corda.explorer.model.CordaWidget
import net.corda.explorer.ui.setCustomCellFactory
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") {
override val root by fxml<TabPane>()
override val root by fxml<BorderPane>()
override val icon = FontAwesomeIcon.HEARTBEAT
override val widgets = listOf(CordaWidget(title, StateMachineViewer.StateMachineWidget())).observable()
private val progressViewTable by fxid<TableView<StateMachineData>>()
private val doneViewTable by fxid<TableView<StateMachineData>>()
private val errorViewTable by fxid<TableView<StateMachineData>>()
private val allViewTable by fxid<TableView<StateMachineData>>()
private val matchingFlowsLabel by fxid<Label>()
private val stateMachinesAll by observableList(StateMachineDataModel::stateMachinesAll)
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 {
right {
label {
textProperty().bind(Bindings.size(flows).map(Number::toString))
textProperty().bind(Bindings.size(stateMachinesAll).map(Number::toString))
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) {
table.apply {
items = tableItems
@ -76,23 +68,21 @@ class StateMachineViewer : CordaView("Flow Triage") {
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
maxWidth = 200.0
}.setCustomCellFactory {
label("$it") {
val hash = SecureHash.sha256(it.toString())
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("Initiator", StateMachineData::flowInitiator).cellFormat { text = FlowInitiatorFormatter.format(it) }
column("Flow Status", StateMachineData::stateMachineStatus).cellFormat {
if (it == null)
text = "No progress data"
else text = it.status
} // TODO null
column("Flow Status", StateMachineData::stateMachineStatus).cellFormat { text = it.status ?: "No progress data" }
column("Result", StateMachineData::addRmStatus).setCustomCellFactory {
if (it is StateMachineStatus.Removed) {
if (it.result.error == null) {
@ -115,8 +105,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
}
else {
label("In progress") {
// TODO Other icons: spnner, hourglass-half, hourglass-1, send-o, space-shuttle ;)
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply {
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply { // Blazing fast! Try not to blink.
glyphSize = 15.0
textAlignment = TextAlignment.CENTER
style = "-fx-fill: lightslategrey"
@ -128,15 +117,33 @@ class StateMachineViewer : CordaView("Flow Triage") {
}
init {
makeColumns(progressViewTable, stateMachinesInProgress, false)
makeColumns(doneViewTable, stateMachinesFinished)
makeColumns(errorViewTable, stateMachinesError)
val searchField = SearchField(stateMachinesAll, listOf(
"Flow name" to { sm, s -> sm.stateMachineName.contains(s, true) },
"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() {
override val root by fxml<Parent>()
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 flowResultVBox by fxid<VBox>()
@ -144,9 +151,10 @@ class StateMachineViewer : CordaView("Flow Triage") {
flowNameLabel.apply {
text = FlowNameFormatter.boring.format(smmData.stateMachineName)
}
flowProgressPane.apply {
content = label {
text = smmData.stateMachineStatus.value?.status // TODO later we can do some magic with showing progress steps with subflows
//TODO It would be nice to have flow graph with showing progress steps with subflows + timestamps (left it for second iteration).
flowProgressVBox.apply {
label {
text = smmData.stateMachineStatus.value?.status ?: "No progress data"
}
}
when (smmData.flowInitiator) {
@ -164,27 +172,27 @@ class StateMachineViewer : CordaView("Flow Triage") {
private fun <T>makeResultVBox(vbox: VBox, result: T) {
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) {
// scrollpane {
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
// TODO Make link to transaction view
vbox.apply {
label("Signed transaction")
label("Signed transaction").apply { style { fontWeight = FontWeight.BOLD } }
label {
text = result.id.toString()
graphic = identicon(result.id, 30.0)
tooltip = identiconToolTip(result.id)
}
// }
}
} 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 {
// TODO Here we could have sth different than SignedTransaction
vbox.apply { label(result.toString()) }
// TODO Here we could have sth different than SignedTransaction/Unit
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 {
spacing = 10.0
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) {
val title = gridPane.lookup("#flowInitiatorTitle") as Label
title.apply {
@ -226,9 +228,6 @@ class StateMachineViewer : CordaView("Flow Triage") {
text = "Flow started by a peer node"
}
gridPane.apply{
// scrollpane { // TODO scrollbar vbox + hbox
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
row {
label("Legal name: ") {
gridpaneConstraints { hAlignment = HPos.LEFT }
@ -236,7 +235,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
minWidth = 150.0
prefWidth = 150.0
}
label(initiator.party.name) { gridpaneConstraints { hAlignment = HPos.LEFT } }
label(initiator.party.name.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
}
row {
label("Owning key: ") {
@ -269,7 +268,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
// TODO test
private fun makeScheduledGrid(gridPane: GridPane, initiator: FlowInitiator.Scheduled) {
val title = gridPane.lookup("flowInitiatorTitle") as Label
val title = gridPane.lookup("#flowInitiatorTitle") as Label
title.apply {
text = "Flow started as scheduled activity"
}
@ -280,7 +279,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
style { fontWeight = FontWeight.BOLD }
prefWidth = 150.0
}
label(initiator.scheduledState.ref.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
label(initiator.scheduledState.ref.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
}
row {
label("Scheduled at: ") {
@ -288,7 +287,7 @@ class StateMachineViewer : CordaView("Flow Triage") {
style { fontWeight = FontWeight.BOLD }
prefWidth = 150.0
}
label(initiator.scheduledState.scheduledAt.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
label(initiator.scheduledState.scheduledAt.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
}
}
}

View File

@ -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) },
"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) } },
"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 } } },
"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
// Transaction table
@ -163,6 +163,7 @@ class TransactionViewer : CordaView("Transactions") {
prefHeight = 400.0
}.apply {
// 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
maxWidth = 26.0
}

View File

@ -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
* 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) },
"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) {
button("New Transaction", FontAwesomeIconView(FontAwesomeIcon.PLUS)) {

View File

@ -269,4 +269,11 @@
.connection-bank-to-regulator {
-fx-stroke: red;
}
}
/* Flow details */
.smm-detail-grid {
-fx-border-color: -color-3;
-fx-border-width: 1;
-fx-border-radius: 2;
}

View File

@ -2,47 +2,54 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.ColumnConstraints?>
<?import javafx.scene.layout.GridPane?>
<?import javafx.scene.layout.RowConstraints?>
<?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>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
<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"/>
<Label fx:id="flowNameLabel" style="-fx-font-weight: bold"/>
</TitledPane>
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator"
GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
<GridPane fx:id="flowInitiatorGrid" vgap="10.0" hgap="10.0">
<padding>
<Insets bottom="5.0" left="5.0" right="10.0" top="10.0" />
</padding>
<!--TODO text styling-->
<Label fx:id="flowInitiatorTitle" GridPane.columnSpan="2" GridPane.rowIndex="0" GridPane.columnIndex="1" GridPane.halignment="LEFT"/>
</GridPane>
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator" GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
<ScrollPane maxHeight="Infinity" minHeight="120">
<GridPane fx:id="flowInitiatorGrid" hgap="10.0" vgap="10.0">
<padding>
<Insets bottom="5.0" left="5.0" right="10.0" top="10.0" />
</padding>
<Label fx:id="flowInitiatorTitle" GridPane.columnIndex="0" GridPane.columnSpan="2" GridPane.halignment="LEFT" GridPane.rowIndex="0" style="-fx-font-weight: bold"/>
</GridPane>
</ScrollPane>
</TitledPane>
<TitledPane fx:id="flowResultPane" collapsible="false" maxHeight="Infinity" text="Result" GridPane.columnIndex="1" GridPane.rowIndex="1">
<VBox fx:id="flowResultVBox" spacing="10.0">
<ScrollPane maxHeight="Infinity">
<VBox fx:id="flowResultVBox" spacing="10.0">
<padding>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
</VBox>
</ScrollPane>
</TitledPane>
<TitledPane fx:id="flowProgressPane" collapsible="false" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
<!--TODO add progress graph-->
<VBox fx:id="flowProgressVBox" spacing="10.0">
<padding>
<Insets bottom="5" left="5" right="5" top="5"/>
<Insets bottom="5" left="5" right="5" top="5" />
</padding>
</VBox>
</TitledPane>
<TitledPane fx:id="flowProgressPane" collapsible="false" maxWidth="Infinity" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
<!--TODO add progress graph-->
</TitledPane>
<columnConstraints>
<ColumnConstraints minWidth="450.0"/>
<ColumnConstraints hgrow="ALWAYS"/>
<ColumnConstraints minWidth="450.0" />
<ColumnConstraints hgrow="ALWAYS" />
</columnConstraints>
<rowConstraints>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS"/>
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
<RowConstraints vgrow="ALWAYS" />
</rowConstraints>
</GridPane>

View File

@ -3,36 +3,22 @@
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.TableView?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.control.Label?>
<?import javafx.scene.control.TabPane?>
<?import javafx.scene.control.Tab?>
<TabPane stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.112" xmlns:fx="http://javafx.com/fxml/1"
tabClosingPolicy="UNAVAILABLE">
<BorderPane stylesheets="@../css/corda.css" xmlns="http://javafx.com/javafx/8.0.76-ea"
xmlns:fx="http://javafx.com/fxml/1">
<padding>
<Insets bottom="5" left="5" right="5" top="5"/>
<Insets top="5" left="5" right="5" bottom="5"/>
</padding>
<Tab text="In progress" fx:id="progressFlowsTab">
<TableView fx:id="progressViewTable" VBox.vgrow="ALWAYS">
<center>
<TableView fx:id="allViewTable" VBox.vgrow="ALWAYS">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</columnResizePolicy>
</TableView>
</Tab>
<Tab text="Finished" fx:id="doneFlowsTab">
<TableView fx:id="doneViewTable" VBox.vgrow="ALWAYS">
<columnResizePolicy>
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
</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>
</center>
<bottom>
<Label fx:id="matchingFlowsLabel" text="matching flows(s)"/>
</bottom>
</BorderPane>