mirror of
https://github.com/corda/corda.git
synced 2025-01-16 01:40:17 +00:00
Add StateMachineViewer and data model to explorer.
Add state machine details with flow result or error, flow initiator, flow name and progress. Split flows into categories: in progress, errored, done.
This commit is contained in:
parent
f9ca498cb8
commit
c734f625ad
@ -6,31 +6,15 @@ 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.StateMachineInfo
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.node.services.NetworkMapCache.MapChange
|
||||
import net.corda.core.node.services.StateMachineTransactionMapping
|
||||
import net.corda.core.node.services.Vault
|
||||
import net.corda.core.seconds
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.core.utilities.ProgressTracker
|
||||
import rx.Observable
|
||||
import rx.subjects.PublishSubject
|
||||
|
||||
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String, val currentState: ProgressTracker?) { // TODO: RG Not a string, but a proper tracking object.
|
||||
companion object {
|
||||
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
|
||||
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
|
||||
val (current, future) = pair
|
||||
|
||||
future.map { ProgressTrackingEvent(stateMachine.id, it, null ) }.startWith(ProgressTrackingEvent(stateMachine.id, current, null))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* This model exposes raw event streams to and from the node.
|
||||
*/
|
||||
|
@ -0,0 +1,86 @@
|
||||
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 rx.Observable
|
||||
|
||||
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
|
||||
companion object {
|
||||
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
|
||||
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
|
||||
val (current, future) = pair
|
||||
future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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?>
|
||||
)
|
||||
|
||||
class StateMachineDataModel {
|
||||
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||
|
||||
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
val flowInitiator= update.stateMachineInfo.initiator
|
||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName, flowInitiator))
|
||||
map[update.id] = added
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
val added = map[update.id]
|
||||
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
||||
added.set(StateMachineStatus.Removed(update.result))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
@ -1,9 +1,6 @@
|
||||
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 javafx.collections.ObservableList
|
||||
import javafx.collections.ObservableMap
|
||||
import net.corda.client.jfx.utils.*
|
||||
@ -11,8 +8,6 @@ import net.corda.core.contracts.ContractState
|
||||
import net.corda.core.contracts.StateAndRef
|
||||
import net.corda.core.contracts.StateRef
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import org.fxmisc.easybind.EasyBind
|
||||
|
||||
@ -59,159 +54,17 @@ data class PartiallyResolvedTransaction(
|
||||
}
|
||||
}
|
||||
|
||||
data class FlowStatus(val status: String)
|
||||
//todo after rebase - remove it rather
|
||||
//class FlowStatus(
|
||||
// val status: String,
|
||||
// pt: ProgressTrackingEvent?,
|
||||
// val progress: FlowLogic.ProgressTrackerDisplayProxy = convert(pt)
|
||||
//) {
|
||||
//
|
||||
// companion object {
|
||||
// fun convert(pt: ProgressTrackingEvent?) : FlowLogic.ProgressTrackerDisplayProxy {
|
||||
// return FlowLogic.ProgressTrackerDisplayProxy(null, false, false)
|
||||
///* pt?. == ProgressTracker.DONE,
|
||||
// pt == null)*/
|
||||
// }
|
||||
// }
|
||||
//}
|
||||
|
||||
sealed class StateMachineStatus {
|
||||
abstract val stateMachineName: String
|
||||
|
||||
data class Added(override val stateMachineName: String) : StateMachineStatus()
|
||||
data class Removed(override val stateMachineName: String) : StateMachineStatus()
|
||||
}
|
||||
|
||||
/*data class StateMachineData(
|
||||
val id: StateMachineRunId,
|
||||
val flowStatus: ObservableValue<FlowStatus?>,
|
||||
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
||||
)
|
||||
*/
|
||||
|
||||
data class StateMachineData(
|
||||
val id: StateMachineRunId,
|
||||
val flowStatus: FlowStatus?,
|
||||
val stateMachineStatus: StateMachineStatus
|
||||
)
|
||||
|
||||
|
||||
// TODO Do we want to have mapping tx <-> StateMachine?
|
||||
/**
|
||||
* This model provides an observable list of transactions and what state machines/flows recorded them
|
||||
*/
|
||||
class TransactionDataModel {
|
||||
private val transactions by observable(NodeMonitorModel::transactions)
|
||||
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
||||
|
||||
private val collectedTransactions = transactions.recordInSequence()
|
||||
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
||||
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
|
||||
map[update.id] = added
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
val added = map[update.id]
|
||||
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
||||
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||
Bindings.createObjectBinding({ StateMachineData(id, progress.value?.message?.let(::FlowStatus), status.get()) }, arrayOf(progress, status))
|
||||
}.getObservableValues().flatten()
|
||||
|
||||
/*
|
||||
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||
}.getObservableValues()*/
|
||||
|
||||
// TODO : Create a new screen for state machines.
|
||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||
val partiallyResolvedTransactions = collectedTransactions.map {
|
||||
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class StateMachineDataModel {
|
||||
private val transactions by observable(NodeMonitorModel::transactions)
|
||||
|
||||
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||
|
||||
private val progressList = progressEvents.getObservableValues()
|
||||
|
||||
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
||||
private val collectedTransactions = transactions.recordInSequence()
|
||||
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
||||
|
||||
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
|
||||
println("**** $update")
|
||||
when (update) {
|
||||
is StateMachineUpdate.Added -> {
|
||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName))
|
||||
map[update.id] = added
|
||||
}
|
||||
is StateMachineUpdate.Removed -> {
|
||||
val added = map[update.id]
|
||||
added ?: throw Exception("State machine removed with unknown id ${update.id}")
|
||||
added.set(StateMachineStatus.Removed(added.value.stateMachineName))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||
Bindings.createObjectBinding({ StateMachineData(id, progress.value?.message?.let(::FlowStatus), status.get()) }, arrayOf(progress, status))
|
||||
}.getObservableValues().flatten()
|
||||
|
||||
/*
|
||||
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||
}.getObservableValues()*/
|
||||
|
||||
// TODO : Create a new screen for state machines.
|
||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||
|
||||
|
||||
|
||||
|
||||
val partiallyResolvedTransactions = collectedTransactions.map {
|
||||
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
||||
}
|
||||
val flowsInProgress = partiallyResolvedTransactions
|
||||
|
||||
/*
|
||||
data class SomeStructure(
|
||||
val someInt: ObservableValue<StateMachineStatus>,
|
||||
val otherData: ObservableValue<String>
|
||||
)
|
||||
*/
|
||||
val progressObservableList = progressList.map{ struct -> struct.message }
|
||||
|
||||
fun asd() {
|
||||
progressEvents // .flatMap { it -> it.key}
|
||||
progressEvents.keys.map{ it -> { if (true) {null }else { it}}}
|
||||
// val a1 = progressEvents.map{ struct -> struct.key. { if ( true) { null } else {struct }} }
|
||||
// val a: ObservableList<SomeStructure> = null!!
|
||||
// val x: ObservableList<SomeStructure> = ObservableList<SomeStructure>();
|
||||
// val a3: ObservableList<SomeStructure> = listOf(Pair(1,"hey"))
|
||||
// val x = a.map{ struct -> struct. { if ( it.javaClass == StateMachineStatus::Removed) { null} else {struct }} }
|
||||
// val b = a.map { struct -> struct.someInt.map { if (it.javaClass == StateMachineStatus::Removed) { null } else { struct } } }
|
||||
// val c = b.flatten().filterNotNull()
|
||||
//return c
|
||||
}
|
||||
|
||||
}
|
@ -30,7 +30,6 @@ import java.util.*
|
||||
@CordaSerializable
|
||||
data class StateMachineInfo(
|
||||
val id: StateMachineRunId,
|
||||
val sessionId: Long,
|
||||
val flowLogicClassName: String,
|
||||
val initiator: FlowInitiator,
|
||||
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
|
||||
|
@ -131,7 +131,7 @@ class Main : App(MainView::class) {
|
||||
// Stock Views.
|
||||
registerView<Dashboard>()
|
||||
registerView<TransactionViewer>()
|
||||
registerView<FlowViewer>()
|
||||
registerView<StateMachineViewer>()
|
||||
// CordApps Views.
|
||||
registerView<CashViewer>()
|
||||
// Tools.
|
||||
|
@ -0,0 +1,14 @@
|
||||
package net.corda.explorer.formatters
|
||||
|
||||
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.Shell -> "Started via shell"
|
||||
is FlowInitiator.Peer -> "Peer legal name: " + value.party.name //TODO format that
|
||||
is FlowInitiator.RPC -> "Rpc username: " + value.username
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package net.corda.explorer.formatters
|
||||
|
||||
object FlowNameFormatter {
|
||||
val boring = object : Formatter<String> {
|
||||
override fun format(value: String) = value.split('.').last().replace("$", ": ") // TODO Better handling of names.
|
||||
}
|
||||
}
|
@ -1,43 +1,55 @@
|
||||
package net.corda.explorer.views
|
||||
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
|
||||
import javafx.beans.binding.Bindings
|
||||
import javafx.beans.property.ReadOnlyObjectWrapper
|
||||
import javafx.collections.ObservableList
|
||||
import javafx.geometry.HPos
|
||||
import javafx.geometry.Insets
|
||||
import javafx.geometry.Pos
|
||||
import javafx.scene.control.TableColumn
|
||||
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
|
||||
import javafx.scene.text.FontWeight
|
||||
import javafx.scene.text.TextAlignment
|
||||
import net.corda.client.jfx.model.StateMachineData
|
||||
import net.corda.client.jfx.model.StateMachineDataModel
|
||||
import net.corda.client.jfx.model.StateMachineStatus
|
||||
import net.corda.client.jfx.model.observableList
|
||||
import net.corda.client.jfx.model.observableListReadOnly
|
||||
import net.corda.client.jfx.utils.map
|
||||
//import net.corda.client.fxutils.map
|
||||
//import net.corda.client.model.StateMachineDataModel
|
||||
//import net.corda.client.model.observableListReadOnly
|
||||
import net.corda.core.crypto.SecureHash
|
||||
import net.corda.core.crypto.toBase58String
|
||||
import net.corda.core.flows.FlowInitiator
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
import net.corda.explorer.formatters.FlowInitiatorFormatter
|
||||
import net.corda.explorer.formatters.FlowNameFormatter
|
||||
import net.corda.explorer.identicon.identicon
|
||||
import net.corda.explorer.identicon.identiconToolTip
|
||||
import net.corda.explorer.model.CordaView
|
||||
import net.corda.explorer.model.CordaWidget
|
||||
import net.corda.explorer.ui.setCustomCellFactory
|
||||
import tornadofx.column
|
||||
import tornadofx.label
|
||||
import tornadofx.observable
|
||||
import tornadofx.right
|
||||
import tornadofx.*
|
||||
|
||||
|
||||
class FlowViewer : CordaView("Flow Triage") {
|
||||
override val root by fxml<BorderPane>()
|
||||
// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows and other communication)
|
||||
class StateMachineViewer : CordaView("Flow Triage") {
|
||||
override val root by fxml<TabPane>()
|
||||
override val icon = FontAwesomeIcon.HEARTBEAT
|
||||
override val widgets = listOf(CordaWidget(title, FlowViewer.StateMachineWidget())).observable()
|
||||
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 flowViewTable by fxid<TableView<FlowViewer.Flow>>()
|
||||
private val flowColumnSessionId by fxid<TableColumn<Flow, Int>>()
|
||||
private val flowColumnInternalId by fxid<TableColumn<Flow, String>>()
|
||||
private val flowColumnState by fxid<TableColumn<Flow, String>>()
|
||||
|
||||
private class StateMachineWidget() : BorderPane() {
|
||||
private val flows by observableListReadOnly(StateMachineDataModel::flowsInProgress)
|
||||
private class StateMachineWidget : BorderPane() {
|
||||
private val flows by observableListReadOnly(StateMachineDataModel::stateMachinesInProgress)
|
||||
|
||||
// TODO can add stats: in progress, errored, maybe done to the widget?
|
||||
init {
|
||||
right {
|
||||
label {
|
||||
@ -48,284 +60,276 @@ class FlowViewer : CordaView("Flow Triage") {
|
||||
}
|
||||
}
|
||||
|
||||
data class Flow(val id: String, val latestProgress: String)
|
||||
private val stateMachinesInProgress by observableList(StateMachineDataModel::stateMachinesInProgress)
|
||||
private val stateMachinesFinished by observableList(StateMachineDataModel::stateMachinesFinished)
|
||||
private val stateMachinesError by observableList(StateMachineDataModel::stateMachinesError)
|
||||
|
||||
private val stateMachines by observableListReadOnly(StateMachineDataModel::stateMachineDataList)
|
||||
|
||||
// private val flows = stateMachines.map { it -> Flow(it.id.toString(), it.flowStatus.map { it.toString() }) }.filtered { ! it.latestProgress.value.contains("Done") }
|
||||
|
||||
private val flows = stateMachines.map { it -> Flow(it.id.toString(), it.flowStatus.toString()) }.filtered {
|
||||
println("--> $it")
|
||||
println("Status:${it.latestProgress}")
|
||||
//it.id.startsWith("[3") or it.id.startsWith("[9")
|
||||
println(it.latestProgress)
|
||||
println(it.latestProgress.contains("status=Done"))
|
||||
!it.latestProgress.contains("status=Done") && it.latestProgress != "null"
|
||||
|
||||
//it.latestProgress != null
|
||||
}
|
||||
|
||||
init {
|
||||
flowViewTable.apply {
|
||||
items = flows
|
||||
column("ID", Flow::id) { maxWidth = 200.0 }
|
||||
.setCustomCellFactory {
|
||||
label("$it") {
|
||||
val hash = SecureHash.Companion.randomSHA256()
|
||||
graphic = identicon(hash, 15.0)
|
||||
tooltip = identiconToolTip(hash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flowColumnSessionId.apply {
|
||||
setCellValueFactory {
|
||||
ReadOnlyObjectWrapper(0)
|
||||
fun makeColumns(table: TableView<StateMachineData>, tableItems: ObservableList<StateMachineData>, withResult: Boolean = true) {
|
||||
table.apply {
|
||||
items = tableItems
|
||||
if (withResult) {
|
||||
rowExpander(expandOnDoubleClick = true) {
|
||||
add(StateMachineDetailsView(it).root)
|
||||
}.apply {
|
||||
// Column stays the same size, but we don't violate column restricted resize policy for the whole table view.
|
||||
minWidth = 26.0
|
||||
maxWidth = 26.0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flowColumnInternalId.setCellValueFactory {
|
||||
ReadOnlyObjectWrapper(it.value.id)
|
||||
}
|
||||
|
||||
flowColumnState.setCellValueFactory {
|
||||
ReadOnlyObjectWrapper(it.value.latestProgress)
|
||||
// it.value.latestProgress
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
/*
|
||||
class StateMachineViewer2 : CordaView("Transactions") {
|
||||
override val root by fxml<BorderPane>()
|
||||
override val icon = FontAwesomeIcon.RANDOM
|
||||
|
||||
private val transactionViewTable by fxid<TableView<Transaction>>()
|
||||
private val matchingTransactionsLabel by fxid<Label>()
|
||||
// Inject data
|
||||
private val transactions by observableListReadOnly(TransactionDataModel::partiallyResolvedTransactions)
|
||||
private val reportingExchange by observableValue(ReportingCurrencyModel::reportingExchange)
|
||||
private val reportingCurrency by observableValue(ReportingCurrencyModel::reportingCurrency)
|
||||
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
|
||||
|
||||
override val widgets = listOf(CordaWidget(title, TransactionWidget())).observable()
|
||||
|
||||
/**
|
||||
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
||||
* have the data.
|
||||
*/
|
||||
data class Transaction(
|
||||
val tx: PartiallyResolvedTransaction,
|
||||
val id: SecureHash,
|
||||
val inputs: Inputs,
|
||||
val outputs: ObservableList<StateAndRef<ContractState>>,
|
||||
val inputParties: ObservableList<List<ObservableValue<NodeInfo?>>>,
|
||||
val outputParties: ObservableList<List<ObservableValue<NodeInfo?>>>,
|
||||
val commandTypes: List<Class<CommandData>>,
|
||||
val totalValueEquiv: ObservableValue<AmountDiff<Currency>>
|
||||
)
|
||||
|
||||
data class Inputs(val resolved: ObservableList<StateAndRef<ContractState>>, val unresolved: ObservableList<StateRef>)
|
||||
|
||||
/**
|
||||
* We map the gathered data about transactions almost one-to-one to the nodes.
|
||||
*/
|
||||
init {
|
||||
val transactions = transactions.map {
|
||||
val resolved = it.inputs.sequence()
|
||||
.map { it as? PartiallyResolvedTransaction.InputResolution.Resolved }
|
||||
.filterNotNull()
|
||||
.map { it.stateAndRef }
|
||||
val unresolved = it.inputs.sequence()
|
||||
.map { it as? PartiallyResolvedTransaction.InputResolution.Unresolved }
|
||||
.filterNotNull()
|
||||
.map { it.stateRef }
|
||||
val outputs = it.transaction.tx.outputs
|
||||
.mapIndexed { index, transactionState ->
|
||||
val stateRef = StateRef(it.id, index)
|
||||
StateAndRef(transactionState, stateRef)
|
||||
}.observable()
|
||||
Transaction(
|
||||
tx = it,
|
||||
id = it.id,
|
||||
inputs = Inputs(resolved, unresolved),
|
||||
outputs = outputs,
|
||||
inputParties = resolved.getParties(),
|
||||
outputParties = outputs.getParties(),
|
||||
commandTypes = it.transaction.tx.commands.map { it.value.javaClass },
|
||||
totalValueEquiv = ::calculateTotalEquiv.lift(myIdentity,
|
||||
reportingExchange,
|
||||
resolved.map { it.state.data }.lift(),
|
||||
it.transaction.tx.outputs.map { it.data }.lift())
|
||||
)
|
||||
}
|
||||
|
||||
val searchField = SearchField(transactions,
|
||||
"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?.contains(s, true) ?: false } } },
|
||||
"Output Party" to { tx, s -> tx.outputParties.any { it.any { it.value?.legalIdentity?.name?.contains(s, true) ?: false } } },
|
||||
"Command Type" to { tx, s -> tx.commandTypes.any { it.simpleName.contains(s, true) } }
|
||||
)
|
||||
root.top = searchField.root
|
||||
// Transaction table
|
||||
transactionViewTable.apply {
|
||||
items = searchField.filteredData
|
||||
column("Transaction ID", Transaction::id) { maxWidth = 200.0 }.setCustomCellFactory {
|
||||
column("ID", StateMachineData::id) { // TODO kill that ID column
|
||||
minWidth = 100.0
|
||||
maxWidth = 200.0
|
||||
}.setCustomCellFactory {
|
||||
label("$it") {
|
||||
graphic = identicon(it, 15.0)
|
||||
tooltip = identiconToolTip(it)
|
||||
val hash = SecureHash.sha256(it.toString())
|
||||
graphic = identicon(hash, 15.0)
|
||||
tooltip = identiconToolTip(hash) //TODO Have id instead of hash.
|
||||
}
|
||||
}
|
||||
column("Input", Transaction::inputs).cellFormat {
|
||||
text = it.resolved.toText()
|
||||
if (!it.unresolved.isEmpty()) {
|
||||
if (!text.isBlank()) {
|
||||
text += ", "
|
||||
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("Result", StateMachineData::addRmStatus).setCustomCellFactory {
|
||||
if (it is StateMachineStatus.Removed) {
|
||||
if (it.result.error == null) {
|
||||
label("Success") {
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.CHECK).apply {
|
||||
glyphSize = 15.0
|
||||
textAlignment = TextAlignment.CENTER
|
||||
style = "-fx-fill: green"
|
||||
}
|
||||
}
|
||||
} else {
|
||||
label("Error") {
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.BOLT).apply {
|
||||
glyphSize = 15.0
|
||||
textAlignment = TextAlignment.CENTER
|
||||
style = "-fx-fill: -color-4"
|
||||
}
|
||||
}
|
||||
}
|
||||
text += "Unresolved(${it.unresolved.size})"
|
||||
}
|
||||
}
|
||||
column("Output", Transaction::outputs).cellFormat { text = it.toText() }
|
||||
column("Input Party", Transaction::inputParties).cellFormat { text = it.flatten().map { it.value?.legalIdentity?.name }.filterNotNull().toSet().joinToString() }
|
||||
column("Output Party", Transaction::outputParties).cellFormat { text = it.flatten().map { it.value?.legalIdentity?.name }.filterNotNull().toSet().joinToString() }
|
||||
column("Command type", Transaction::commandTypes).cellFormat { text = it.map { it.simpleName }.joinToString() }
|
||||
column("Total value", Transaction::totalValueEquiv).cellFormat {
|
||||
text = "${it.positivity.sign}${AmountFormatter.boring.format(it.amount)}"
|
||||
titleProperty.bind(reportingCurrency.map { "Total value ($it equiv)" })
|
||||
}
|
||||
|
||||
rowExpander {
|
||||
add(ContractStatesView(it).root)
|
||||
prefHeight = 400.0
|
||||
}.apply {
|
||||
prefWidth = 26.0
|
||||
isResizable = false
|
||||
}
|
||||
setColumnResizePolicy { true }
|
||||
}
|
||||
matchingTransactionsLabel.textProperty().bind(Bindings.size(transactionViewTable.items).map {
|
||||
"$it matching transaction${if (it == 1) "" else "s"}"
|
||||
})
|
||||
}
|
||||
|
||||
private fun ObservableList<StateAndRef<ContractState>>.getParties() = map { it.state.data.participants.map { getModel<NetworkIdentityModel>().lookup(it) } }
|
||||
private fun ObservableList<StateAndRef<ContractState>>.toText() = map { it.contract().javaClass.simpleName }.groupBy { it }.map { "${it.key} (${it.value.size})" }.joinToString()
|
||||
|
||||
private class TransactionWidget() : BorderPane() {
|
||||
private val partiallyResolvedTransactions by observableListReadOnly(TransactionDataModel::partiallyResolvedTransactions)
|
||||
|
||||
// TODO : Add a scrolling table to show latest transaction.
|
||||
// TODO : Add a chart to show types of transactions.
|
||||
init {
|
||||
right {
|
||||
label {
|
||||
textProperty().bind(Bindings.size(partiallyResolvedTransactions).map(Number::toString))
|
||||
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
|
||||
else {
|
||||
label("In progress") {
|
||||
// TODO Other icons: spnner, hourglass-half, hourglass-1, send-o, space-shuttle ;)
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.ROCKET).apply {
|
||||
glyphSize = 15.0
|
||||
textAlignment = TextAlignment.CENTER
|
||||
style = "-fx-fill: lightslategrey"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ContractStatesView(transaction: Transaction) : Fragment() {
|
||||
init {
|
||||
makeColumns(progressViewTable, stateMachinesInProgress, false)
|
||||
makeColumns(doneViewTable, stateMachinesFinished)
|
||||
makeColumns(errorViewTable, stateMachinesError)
|
||||
}
|
||||
|
||||
private inner class StateMachineDetailsView(val smmData: StateMachineData) : Fragment() {
|
||||
override val root by fxml<Parent>()
|
||||
private val inputs by fxid<ListView<StateAndRef<ContractState>>>()
|
||||
private val outputs by fxid<ListView<StateAndRef<ContractState>>>()
|
||||
private val signatures by fxid<VBox>()
|
||||
private val inputPane by fxid<TitledPane>()
|
||||
private val outputPane by fxid<TitledPane>()
|
||||
private val signaturesPane by fxid<TitledPane>()
|
||||
private val flowNamePane by fxid<TitledPane>()
|
||||
private val flowProgressPane by fxid<TitledPane>()
|
||||
private val flowInitiatorPane by fxid<TitledPane>()
|
||||
private val flowResultPane by fxid<TitledPane>()
|
||||
|
||||
init {
|
||||
val signatureData = transaction.tx.transaction.sigs.map { it.by }
|
||||
// Bind count to TitlePane
|
||||
inputPane.text = "Input (${transaction.inputs.resolved.count()})"
|
||||
outputPane.text = "Output (${transaction.outputs.count()})"
|
||||
signaturesPane.text = "Signatures (${signatureData.count()})"
|
||||
|
||||
inputs.cellCache { getCell(it) }
|
||||
outputs.cellCache { getCell(it) }
|
||||
|
||||
inputs.items = transaction.inputs.resolved
|
||||
outputs.items = transaction.outputs.observable()
|
||||
|
||||
signatures.children.addAll(signatureData.map { signature ->
|
||||
val nodeInfo = getModel<NetworkIdentityModel>().lookup(signature)
|
||||
copyableLabel(nodeInfo.map { "${signature.toStringShort()} (${it?.legalIdentity?.name ?: "???"})" })
|
||||
})
|
||||
}
|
||||
|
||||
private fun getCell(contractState: StateAndRef<ContractState>): Node {
|
||||
return {
|
||||
gridpane {
|
||||
padding = Insets(0.0, 5.0, 10.0, 10.0)
|
||||
vgap = 10.0
|
||||
hgap = 10.0
|
||||
row {
|
||||
label("${contractState.contract().javaClass.simpleName} (${contractState.ref.toString().substring(0, 16)}...)[${contractState.ref.index}]") {
|
||||
graphic = identicon(contractState.ref.txhash, 30.0)
|
||||
tooltip = identiconToolTip(contractState.ref.txhash)
|
||||
gridpaneConstraints { columnSpan = 2 }
|
||||
}
|
||||
}
|
||||
val data = contractState.state.data
|
||||
when (data) {
|
||||
is Cash.State -> {
|
||||
row {
|
||||
label("Amount :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
|
||||
label(AmountFormatter.boring.format(data.amount.withoutIssuer()))
|
||||
}
|
||||
row {
|
||||
label("Issuer :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
|
||||
label("${data.amount.token.issuer}") {
|
||||
tooltip(data.amount.token.issuer.party.owningKey.toBase58String())
|
||||
}
|
||||
}
|
||||
row {
|
||||
label("Owner :") { gridpaneConstraints { hAlignment = HPos.RIGHT } }
|
||||
val owner = data.owner
|
||||
val nodeInfo = getModel<NetworkIdentityModel>().lookup(owner)
|
||||
label(nodeInfo.map { it?.legalIdentity?.name ?: "???" }) {
|
||||
tooltip(data.owner.toBase58String())
|
||||
}
|
||||
}
|
||||
}
|
||||
// TODO : Generic view using reflection?
|
||||
else -> label {}
|
||||
}
|
||||
flowNamePane.apply {
|
||||
content = label {
|
||||
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
|
||||
}
|
||||
}
|
||||
flowInitiatorPane.apply {
|
||||
//TODO use fxml to access initiatorGridPane
|
||||
// initiatorGridPane.apply {when...
|
||||
content = when (smmData.flowInitiator) {
|
||||
is FlowInitiator.Shell -> ShellNode() // TODO Extend this when we will have more information on shell user.
|
||||
is FlowInitiator.Peer -> PeerNode(smmData.flowInitiator as FlowInitiator.Peer)
|
||||
is FlowInitiator.RPC -> RPCNode(smmData.flowInitiator as FlowInitiator.RPC)
|
||||
is FlowInitiator.Scheduled -> ScheduledNode(smmData.flowInitiator as FlowInitiator.Scheduled)
|
||||
}
|
||||
}
|
||||
flowResultPane.apply {
|
||||
val status = smmData.addRmStatus.value
|
||||
if (status is StateMachineStatus.Removed) {
|
||||
content = status.result.match(onValue = { ResultNode(it) }, onError = { ErrorNode(it) })
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun StateAndRef<ContractState>.contract() = this.state.data.contract
|
||||
// TODO make that Vbox part of FXML
|
||||
private inner class ResultNode<T>(result: T) : VBox() {
|
||||
init {
|
||||
spacing = 10.0
|
||||
padding = Insets(5.0, 5.0, 5.0, 5.0)
|
||||
if (result == null) {
|
||||
label("No return value from flow.")
|
||||
} else if (result is SignedTransaction) {
|
||||
// scrollpane {
|
||||
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
||||
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||
// TODO Make link to transaction view
|
||||
label("Signed transaction")
|
||||
label {
|
||||
text = result.id.toString()
|
||||
graphic = identicon(result.id, 30.0)
|
||||
tooltip = identiconToolTip(result.id)
|
||||
}
|
||||
// }
|
||||
} else if (result is Unit) {
|
||||
label("Flow completed with success.")
|
||||
}
|
||||
else {
|
||||
// TODO Here we could have sth different than SignedTransaction
|
||||
label(result.toString())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make that Vbox part of FXML
|
||||
private inner class ErrorNode(val error: Throwable) : VBox() {
|
||||
init {
|
||||
vbox {
|
||||
spacing = 10.0
|
||||
padding = Insets(5.0, 5.0, 5.0, 5.0)
|
||||
label("Error") {
|
||||
graphic = FontAwesomeIconView(FontAwesomeIcon.BOLT).apply {
|
||||
glyphSize = 30
|
||||
textAlignment = TextAlignment.CENTER
|
||||
style = "-fx-fill: -color-4"
|
||||
}
|
||||
}
|
||||
// TODO think of border styling
|
||||
vbox {
|
||||
spacing = 10.0
|
||||
label { text = error::class.simpleName }
|
||||
scrollpane {
|
||||
hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
||||
vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||
label { text = error.message }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private inner class ShellNode : Label() {
|
||||
init {
|
||||
label("Flow started by shell user")
|
||||
}
|
||||
}
|
||||
|
||||
// TODO make it more generic, reuse gridpane definition - to fxml
|
||||
private inner class PeerNode(val initiator: FlowInitiator.Peer): GridPane() {
|
||||
init {
|
||||
gridpane {
|
||||
padding = Insets(0.0, 5.0, 10.0, 10.0)
|
||||
vgap = 10.0
|
||||
hgap = 10.0
|
||||
row {
|
||||
label("Flow started by a peer node") {
|
||||
gridpaneConstraints {
|
||||
columnSpan = 2
|
||||
hAlignment = HPos.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
// scrollpane { // TODO scrollbar vbox + hbox
|
||||
// hbarPolicy = ScrollPane.ScrollBarPolicy.AS_NEEDED
|
||||
// vbarPolicy = ScrollPane.ScrollBarPolicy.NEVER
|
||||
row {
|
||||
label("Legal name: ") {
|
||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||
style { fontWeight = FontWeight.BOLD }
|
||||
minWidth = 150.0
|
||||
prefWidth = 150.0
|
||||
}
|
||||
label(initiator.party.name) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||
}
|
||||
row {
|
||||
label("Owning key: ") {
|
||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||
style { fontWeight = FontWeight.BOLD }
|
||||
minWidth = 150.0
|
||||
prefWidth = 150.0
|
||||
}
|
||||
label(initiator.party.owningKey.toBase58String()) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private inner class RPCNode(val initiator: FlowInitiator.RPC) : GridPane() {
|
||||
init {
|
||||
gridpane {
|
||||
padding = Insets(0.0, 5.0, 10.0, 10.0)
|
||||
vgap = 10.0
|
||||
hgap = 10.0
|
||||
row {
|
||||
label("Flow started by a RPC user") {
|
||||
gridpaneConstraints {
|
||||
columnSpan = 2
|
||||
hAlignment = HPos.CENTER
|
||||
}
|
||||
}
|
||||
}
|
||||
row {
|
||||
label("User name: ") {
|
||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||
style { fontWeight = FontWeight.BOLD }
|
||||
prefWidth = 150.0
|
||||
}
|
||||
label(initiator.username) { gridpaneConstraints { hAlignment = HPos.LEFT } }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO test
|
||||
private inner class ScheduledNode(val initiator: FlowInitiator.Scheduled) : GridPane() {
|
||||
init {
|
||||
gridpane {
|
||||
padding = Insets(0.0, 5.0, 10.0, 10.0)
|
||||
vgap = 10.0
|
||||
hgap = 10.0
|
||||
row {
|
||||
label("Flow started as scheduled activity")
|
||||
gridpaneConstraints {
|
||||
columnSpan = 2
|
||||
hAlignment = HPos.CENTER
|
||||
}
|
||||
}
|
||||
row {
|
||||
label("Scheduled state: ") {
|
||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||
style { fontWeight = FontWeight.BOLD }
|
||||
prefWidth = 150.0
|
||||
}
|
||||
label(initiator.scheduledState.ref.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
|
||||
}
|
||||
row {
|
||||
label("Scheduled at: ") {
|
||||
gridpaneConstraints { hAlignment = HPos.LEFT }
|
||||
style { fontWeight = FontWeight.BOLD }
|
||||
prefWidth = 150.0
|
||||
}
|
||||
label(initiator.scheduledState.scheduledAt.toString()) { gridpaneConstraints { hAlignment = HPos.LEFT } } //TODO format
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash
|
||||
*/
|
||||
private fun calculateTotalEquiv(identity: NodeInfo?,
|
||||
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
|
||||
inputs: List<ContractState>,
|
||||
outputs: List<ContractState>): AmountDiff<Currency> {
|
||||
val (reportingCurrency, exchange) = reportingCurrencyExchange
|
||||
val publicKey = identity?.legalIdentity?.owningKey
|
||||
fun List<ContractState>.sum() = this.map { it as? Cash.State }
|
||||
.filterNotNull()
|
||||
.filter { publicKey == it.owner }
|
||||
.map { exchange(it.amount.withoutIssuer()).quantity }
|
||||
.sum()
|
||||
|
||||
// For issuing cash, if I am the issuer and not the owner (e.g. issuing cash to other party), count it as negative.
|
||||
val issuedAmount = if (inputs.isEmpty()) outputs.map { it as? Cash.State }
|
||||
.filterNotNull()
|
||||
.filter { publicKey == it.amount.token.issuer.party.owningKey && publicKey != it.owner }
|
||||
.map { exchange(it.amount.withoutIssuer()).quantity }
|
||||
.sum() else 0
|
||||
|
||||
return AmountDiff.fromLong(outputs.sum() - inputs.sum() - issuedAmount, reportingCurrency)
|
||||
}
|
||||
|
||||
*/
|
@ -117,7 +117,10 @@ class TransactionViewer : CordaView("Transactions") {
|
||||
// Transaction table
|
||||
transactionViewTable.apply {
|
||||
items = searchField.filteredData
|
||||
column("Transaction ID", Transaction::id) { maxWidth = 200.0 }.setCustomCellFactory {
|
||||
column("Transaction ID", Transaction::id) {
|
||||
minWidth = 20.0
|
||||
maxWidth = 200.0
|
||||
}.setCustomCellFactory {
|
||||
label("$it") {
|
||||
graphic = identicon(it, 15.0)
|
||||
tooltip = identiconToolTip(it)
|
||||
@ -159,10 +162,10 @@ class TransactionViewer : CordaView("Transactions") {
|
||||
add(ContractStatesView(it).root)
|
||||
prefHeight = 400.0
|
||||
}.apply {
|
||||
prefWidth = 26.0
|
||||
isResizable = false
|
||||
// Column stays the same size, but we don't violate column restricted resize policy for the whole table view.
|
||||
minWidth = 26.0
|
||||
maxWidth = 26.0
|
||||
}
|
||||
setColumnResizePolicy { true }
|
||||
}
|
||||
matchingTransactionsLabel.textProperty().bind(Bindings.size(transactionViewTable.items).map {
|
||||
"$it matching transaction${if (it == 1) "" else "s"}"
|
||||
|
@ -1,29 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TableColumn?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.BorderPane?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<BorderPane 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>
|
||||
<center>
|
||||
<TableView fx:id="flowViewTable" VBox.vgrow="ALWAYS">
|
||||
<columnResizePolicy>
|
||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
||||
</columnResizePolicy>
|
||||
<columns>
|
||||
<TableColumn fx:id="flowColumnSessionId" prefWidth="10.0" styleClass="first-column" text="Session ID" />
|
||||
<TableColumn fx:id="flowColumnInternalId" prefWidth="73.0" styleClass="first-column" text="Flow Internal ID" />
|
||||
<TableColumn fx:id="flowColumnState" prefWidth="173.0" styleClass="first-column" text="Flow Status" />
|
||||
</columns>
|
||||
</TableView>
|
||||
</center>
|
||||
<bottom>
|
||||
<Label fx:id="matchingFlowsLabel" text="matching flow(s)" />
|
||||
</bottom>
|
||||
</BorderPane>
|
@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.Label?>
|
||||
<?import javafx.scene.control.TitledPane?>
|
||||
<?import javafx.scene.layout.GridPane?>
|
||||
|
||||
<?import javafx.scene.layout.ColumnConstraints?>
|
||||
<?import javafx.scene.layout.RowConstraints?>
|
||||
<GridPane 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">
|
||||
<Label fx:id="flowNameLabel"/>
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="flowInitiatorPane" collapsible="false" maxHeight="Infinity" text="Flow initiator"
|
||||
GridPane.columnIndex="0" GridPane.fillWidth="true" GridPane.rowIndex="1">
|
||||
<!-- todo add gridpane - from code! -->
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="flowResultPane" collapsible="false" maxHeight="Infinity" text="Result" GridPane.columnIndex="1" GridPane.rowIndex="1">
|
||||
<!-- todo add vbox - from code! -->
|
||||
</TitledPane>
|
||||
<TitledPane fx:id="flowProgressPane" collapsible="false" maxWidth="Infinity" text="Progress steps" GridPane.columnSpan="2" GridPane.rowIndex="2">
|
||||
</TitledPane>
|
||||
<columnConstraints>
|
||||
<ColumnConstraints minWidth="450.0"/>
|
||||
<ColumnConstraints hgrow="ALWAYS"/>
|
||||
</columnConstraints>
|
||||
<rowConstraints>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
<RowConstraints vgrow="ALWAYS"/>
|
||||
</rowConstraints>
|
||||
</GridPane>
|
@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<?import javafx.geometry.Insets?>
|
||||
<?import javafx.scene.control.TableView?>
|
||||
<?import javafx.scene.layout.VBox?>
|
||||
|
||||
<?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">
|
||||
<padding>
|
||||
<Insets bottom="5" left="5" right="5" top="5"/>
|
||||
</padding>
|
||||
<Tab text="In progress" fx:id="progressFlowsTab">
|
||||
<TableView fx:id="progressViewTable" 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>
|
Loading…
Reference in New Issue
Block a user