mirror of
https://github.com/corda/corda.git
synced 2024-12-28 00:38:55 +00:00
Interim checkin
This commit is contained in:
parent
759cb6da04
commit
70e7a94310
@ -2,7 +2,7 @@
|
|||||||
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
<configuration default="false" name="Explorer - demo nodes (simulation)" type="JetRunConfigurationType" factoryName="Kotlin" singleton="true">
|
||||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||||
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
<option name="MAIN_CLASS_NAME" value="net.corda.explorer.MainKt" />
|
||||||
<option name="VM_PARAMETERS" value="" />
|
<option name="VM_PARAMETERS" value="-DAMQ_DELIVERY_DELAY_MS=5000" />
|
||||||
<option name="PROGRAM_PARAMETERS" value="-S" />
|
<option name="PROGRAM_PARAMETERS" value="-S" />
|
||||||
<option name="WORKING_DIRECTORY" value="" />
|
<option name="WORKING_DIRECTORY" value="" />
|
||||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||||
|
@ -16,17 +16,20 @@ import net.corda.core.transactions.SignedTransaction
|
|||||||
import rx.Observable
|
import rx.Observable
|
||||||
import rx.subjects.PublishSubject
|
import rx.subjects.PublishSubject
|
||||||
|
|
||||||
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String) {
|
data class ProgressTrackingEvent(val stateMachineId: StateMachineRunId, val message: String, val sessionId: Long ) { // TODO: RG Not a string, but a proper tracking object.
|
||||||
companion object {
|
companion object {
|
||||||
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
|
fun createStreamFromStateMachineInfo(stateMachine: StateMachineInfo): Observable<ProgressTrackingEvent>? {
|
||||||
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
|
return stateMachine.progressTrackerStepAndUpdates?.let { pair ->
|
||||||
val (current, future) = pair
|
val (current, future) = pair
|
||||||
future.map { ProgressTrackingEvent(stateMachine.id, it) }.startWith(ProgressTrackingEvent(stateMachine.id, current))
|
future.map { ProgressTrackingEvent(stateMachine.id, it, stateMachine.sessionId ) }.startWith(ProgressTrackingEvent(stateMachine.id, current, stateMachine.sessionId))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This model exposes raw event streams to and from the node.
|
* This model exposes raw event streams to and from the node.
|
||||||
*/
|
*/
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
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
|
||||||
@ -10,6 +11,7 @@ import net.corda.core.contracts.ContractState
|
|||||||
import net.corda.core.contracts.StateAndRef
|
import net.corda.core.contracts.StateAndRef
|
||||||
import net.corda.core.contracts.StateRef
|
import net.corda.core.contracts.StateRef
|
||||||
import net.corda.core.crypto.SecureHash
|
import net.corda.core.crypto.SecureHash
|
||||||
|
import net.corda.core.flows.FlowLogic
|
||||||
import net.corda.core.flows.StateMachineRunId
|
import net.corda.core.flows.StateMachineRunId
|
||||||
import net.corda.core.messaging.StateMachineUpdate
|
import net.corda.core.messaging.StateMachineUpdate
|
||||||
import net.corda.core.transactions.SignedTransaction
|
import net.corda.core.transactions.SignedTransaction
|
||||||
@ -59,6 +61,21 @@ data class PartiallyResolvedTransaction(
|
|||||||
}
|
}
|
||||||
|
|
||||||
data class FlowStatus(val status: String)
|
data class FlowStatus(val status: String)
|
||||||
|
//todo after rebase
|
||||||
|
//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 {
|
sealed class StateMachineStatus {
|
||||||
abstract val stateMachineName: String
|
abstract val stateMachineName: String
|
||||||
@ -67,11 +84,19 @@ sealed class StateMachineStatus {
|
|||||||
data class Removed(override val stateMachineName: String) : StateMachineStatus()
|
data class Removed(override val stateMachineName: String) : StateMachineStatus()
|
||||||
}
|
}
|
||||||
|
|
||||||
data class StateMachineData(
|
/*data class StateMachineData(
|
||||||
val id: StateMachineRunId,
|
val id: StateMachineRunId,
|
||||||
val flowStatus: ObservableValue<FlowStatus?>,
|
val flowStatus: ObservableValue<FlowStatus?>,
|
||||||
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
val stateMachineStatus: ObservableValue<StateMachineStatus>
|
||||||
)
|
)
|
||||||
|
*/
|
||||||
|
|
||||||
|
data class StateMachineData(
|
||||||
|
val id: StateMachineRunId,
|
||||||
|
val flowStatus: FlowStatus?,
|
||||||
|
val stateMachineStatus: StateMachineStatus
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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
|
||||||
@ -99,9 +124,25 @@ class TransactionDataModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) {
|
||||||
|
id, status, progress ->
|
||||||
|
Bindings.createObjectBinding(
|
||||||
|
{ StateMachineData(id,
|
||||||
|
// FlowStatus(progress.value?.message!!, null),
|
||||||
|
progress.value?.message?.let{
|
||||||
|
FlowStatus(progress.value!!.message!!.toString(),progress.value)
|
||||||
|
},
|
||||||
|
status.get()) }
|
||||||
|
, arrayOf(progress, status)
|
||||||
|
)
|
||||||
|
}.getObservableValues().flatten()
|
||||||
|
|
||||||
|
/*
|
||||||
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||||
}.getObservableValues()
|
}.getObservableValues()*/
|
||||||
|
|
||||||
// TODO : Create a new screen for state machines.
|
// TODO : Create a new screen for state machines.
|
||||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||||
@ -109,3 +150,85 @@ class TransactionDataModel {
|
|||||||
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
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(progress.value!!.message!!.toString(),null)
|
||||||
|
},
|
||||||
|
// 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,6 +30,7 @@ import java.util.*
|
|||||||
@CordaSerializable
|
@CordaSerializable
|
||||||
data class StateMachineInfo(
|
data class StateMachineInfo(
|
||||||
val id: StateMachineRunId,
|
val id: StateMachineRunId,
|
||||||
|
val sessionId: Long,
|
||||||
val flowLogicClassName: String,
|
val flowLogicClassName: String,
|
||||||
val initiator: FlowInitiator,
|
val initiator: FlowInitiator,
|
||||||
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
|
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
|
||||||
|
@ -131,6 +131,7 @@ class Main : App(MainView::class) {
|
|||||||
// Stock Views.
|
// Stock Views.
|
||||||
registerView<Dashboard>()
|
registerView<Dashboard>()
|
||||||
registerView<TransactionViewer>()
|
registerView<TransactionViewer>()
|
||||||
|
registerView<FlowViewer>()
|
||||||
// CordApps Views.
|
// CordApps Views.
|
||||||
registerView<CashViewer>()
|
registerView<CashViewer>()
|
||||||
// Tools.
|
// Tools.
|
||||||
@ -248,7 +249,7 @@ fun main(args: Array<String>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for (i in 0..maxIterations) {
|
for (i in 0..maxIterations) {
|
||||||
Thread.sleep(300)
|
Thread.sleep(300) //Thread.sleep(5000) -> todo rebase
|
||||||
// Issuer requests.
|
// Issuer requests.
|
||||||
eventGenerator.issuerGenerator.map { command ->
|
eventGenerator.issuerGenerator.map { command ->
|
||||||
when (command) {
|
when (command) {
|
||||||
|
@ -0,0 +1,331 @@
|
|||||||
|
package net.corda.explorer.views
|
||||||
|
|
||||||
|
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
|
||||||
|
import javafx.beans.binding.Bindings
|
||||||
|
import javafx.beans.property.ReadOnlyObjectWrapper
|
||||||
|
import javafx.geometry.Pos
|
||||||
|
import javafx.scene.control.TableColumn
|
||||||
|
import javafx.scene.control.TableView
|
||||||
|
import javafx.scene.layout.BorderPane
|
||||||
|
import net.corda.client.jfx.model.StateMachineDataModel
|
||||||
|
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.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
|
||||||
|
|
||||||
|
|
||||||
|
class FlowViewer : CordaView("Flow Triage") {
|
||||||
|
override val root by fxml<BorderPane>()
|
||||||
|
override val icon = FontAwesomeIcon.HEARTBEAT
|
||||||
|
override val widgets = listOf(CordaWidget(title, FlowViewer.StateMachineWidget())).observable()
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
init {
|
||||||
|
right {
|
||||||
|
label {
|
||||||
|
textProperty().bind(Bindings.size(flows).map(Number::toString))
|
||||||
|
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Flow(val id: String, val latestProgress: String)
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
label("$it") {
|
||||||
|
graphic = identicon(it, 15.0)
|
||||||
|
tooltip = identiconToolTip(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
column("Input", Transaction::inputs).cellFormat {
|
||||||
|
text = it.resolved.toText()
|
||||||
|
if (!it.unresolved.isEmpty()) {
|
||||||
|
if (!text.isBlank()) {
|
||||||
|
text += ", "
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private inner class ContractStatesView(transaction: Transaction) : 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>()
|
||||||
|
|
||||||
|
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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun StateAndRef<ContractState>.contract() = this.state.data.contract
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
*/
|
@ -0,0 +1,29 @@
|
|||||||
|
<?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>
|
Loading…
Reference in New Issue
Block a user