mirror of
https://github.com/corda/corda.git
synced 2024-12-27 08:22:35 +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">
|
||||
<extension name="coverage" enabled="false" merge="false" sample_coverage="true" runner="idea" />
|
||||
<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="WORKING_DIRECTORY" value="" />
|
||||
<option name="ALTERNATIVE_JRE_PATH_ENABLED" value="true" />
|
||||
|
@ -16,17 +16,20 @@ import net.corda.core.transactions.SignedTransaction
|
||||
import rx.Observable
|
||||
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 {
|
||||
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))
|
||||
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.
|
||||
*/
|
||||
|
@ -1,5 +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
|
||||
@ -10,6 +11,7 @@ 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.FlowLogic
|
||||
import net.corda.core.flows.StateMachineRunId
|
||||
import net.corda.core.messaging.StateMachineUpdate
|
||||
import net.corda.core.transactions.SignedTransaction
|
||||
@ -59,6 +61,21 @@ data class PartiallyResolvedTransaction(
|
||||
}
|
||||
|
||||
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 {
|
||||
abstract val stateMachineName: String
|
||||
@ -67,11 +84,19 @@ sealed class StateMachineStatus {
|
||||
data class Removed(override val stateMachineName: String) : StateMachineStatus()
|
||||
}
|
||||
|
||||
data class StateMachineData(
|
||||
/*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
|
||||
)
|
||||
|
||||
|
||||
/**
|
||||
* 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 ->
|
||||
StateMachineData(id, progress.map { it?.let { FlowStatus(it.message) } }, status)
|
||||
}.getObservableValues()
|
||||
}.getObservableValues()*/
|
||||
|
||||
// TODO : Create a new screen for state machines.
|
||||
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||
@ -109,3 +150,85 @@ class TransactionDataModel {
|
||||
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
|
||||
data class StateMachineInfo(
|
||||
val id: StateMachineRunId,
|
||||
val sessionId: Long,
|
||||
val flowLogicClassName: String,
|
||||
val initiator: FlowInitiator,
|
||||
val progressTrackerStepAndUpdates: Pair<String, Observable<String>>?
|
||||
|
@ -131,6 +131,7 @@ class Main : App(MainView::class) {
|
||||
// Stock Views.
|
||||
registerView<Dashboard>()
|
||||
registerView<TransactionViewer>()
|
||||
registerView<FlowViewer>()
|
||||
// CordApps Views.
|
||||
registerView<CashViewer>()
|
||||
// Tools.
|
||||
@ -248,7 +249,7 @@ fun main(args: Array<String>) {
|
||||
}
|
||||
|
||||
for (i in 0..maxIterations) {
|
||||
Thread.sleep(300)
|
||||
Thread.sleep(300) //Thread.sleep(5000) -> todo rebase
|
||||
// Issuer requests.
|
||||
eventGenerator.issuerGenerator.map { 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