mirror of
https://github.com/corda/corda.git
synced 2025-06-19 07:38:22 +00:00
Changing UI layout
This commit is contained in:
@ -16,7 +16,9 @@ import com.r3corda.node.driver.driver
|
|||||||
import com.r3corda.node.internal.CordaRPCOpsImpl
|
import com.r3corda.node.internal.CordaRPCOpsImpl
|
||||||
import com.r3corda.node.services.User
|
import com.r3corda.node.services.User
|
||||||
import com.r3corda.node.services.config.configureTestSSL
|
import com.r3corda.node.services.config.configureTestSSL
|
||||||
|
import com.r3corda.node.services.messaging.ArtemisMessagingComponent
|
||||||
import com.r3corda.node.services.messaging.StateMachineUpdate
|
import com.r3corda.node.services.messaging.StateMachineUpdate
|
||||||
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import com.r3corda.testing.expect
|
import com.r3corda.testing.expect
|
||||||
import com.r3corda.testing.expectEvents
|
import com.r3corda.testing.expectEvents
|
||||||
@ -30,7 +32,6 @@ import java.util.concurrent.CountDownLatch
|
|||||||
import kotlin.concurrent.thread
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
class NodeMonitorModelTest {
|
class NodeMonitorModelTest {
|
||||||
|
|
||||||
lateinit var aliceNode: NodeInfo
|
lateinit var aliceNode: NodeInfo
|
||||||
lateinit var notaryNode: NodeInfo
|
lateinit var notaryNode: NodeInfo
|
||||||
val stopDriver = CountDownLatch(1)
|
val stopDriver = CountDownLatch(1)
|
||||||
@ -67,7 +68,7 @@ class NodeMonitorModelTest {
|
|||||||
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
|
networkMapUpdates = monitor.networkMap.bufferUntilSubscribed()
|
||||||
clientToService = monitor.clientToService
|
clientToService = monitor.clientToService
|
||||||
|
|
||||||
monitor.register(aliceNode, configureTestSSL(), cashUser.username, cashUser.password)
|
monitor.register(ArtemisMessagingComponent.toHostAndPort(aliceNode.address), configureTestSSL(), cashUser.username, cashUser.password)
|
||||||
driverStarted.countDown()
|
driverStarted.countDown()
|
||||||
stopDriver.await()
|
stopDriver.await()
|
||||||
}
|
}
|
||||||
@ -85,20 +86,22 @@ class NodeMonitorModelTest {
|
|||||||
fun `network map update`() {
|
fun `network map update`() {
|
||||||
newNode("Bob")
|
newNode("Bob")
|
||||||
newNode("Charlie")
|
newNode("Charlie")
|
||||||
networkMapUpdates.expectEvents(isStrict = false) {
|
networkMapUpdates.filter { !it.node.advertisedServices.any { it.info.type.isNotary() } }
|
||||||
sequence(
|
.filter { !it.node.advertisedServices.any { it.info.type == NetworkMapService.type } }
|
||||||
// TODO : Add test for remove when driver DSL support individual node shutdown.
|
.expectEvents(isStrict = false) {
|
||||||
expect { output: NetworkMapCache.MapChange ->
|
sequence(
|
||||||
require(output.node.legalIdentity.name == "Alice") { output.node.legalIdentity.name }
|
// TODO : Add test for remove when driver DSL support individual node shutdown.
|
||||||
},
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
expect { output: NetworkMapCache.MapChange ->
|
require(output.node.legalIdentity.name == "Alice") { "Expecting : Alice, Actual : ${output.node.legalIdentity.name}" }
|
||||||
require(output.node.legalIdentity.name == "Bob") { output.node.legalIdentity.name }
|
},
|
||||||
},
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
expect { output: NetworkMapCache.MapChange ->
|
require(output.node.legalIdentity.name == "Bob") { "Expecting : Bob, Actual : ${output.node.legalIdentity.name}" }
|
||||||
require(output.node.legalIdentity.name == "Charlie") { output.node.legalIdentity.name }
|
},
|
||||||
}
|
expect { output: NetworkMapCache.MapChange ->
|
||||||
)
|
require(output.node.legalIdentity.name == "Charlie") { "Expecting : Charlie, Actual : ${output.node.legalIdentity.name}" }
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -10,9 +10,7 @@ import javafx.collections.ObservableList
|
|||||||
import javafx.collections.ObservableMap
|
import javafx.collections.ObservableMap
|
||||||
import javafx.collections.transformation.FilteredList
|
import javafx.collections.transformation.FilteredList
|
||||||
import org.fxmisc.easybind.EasyBind
|
import org.fxmisc.easybind.EasyBind
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
import kotlin.concurrent.thread
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Here follows utility extension functions that help reduce the visual load when developing RX code. Each function should
|
* Here follows utility extension functions that help reduce the visual load when developing RX code. Each function should
|
||||||
|
@ -4,7 +4,6 @@ import com.r3corda.client.fxutils.*
|
|||||||
import com.r3corda.core.contracts.ContractState
|
import com.r3corda.core.contracts.ContractState
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
import com.r3corda.core.contracts.StateRef
|
import com.r3corda.core.contracts.StateRef
|
||||||
import com.r3corda.client.fxutils.recordInSequence
|
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
@ -15,8 +14,6 @@ import javafx.beans.value.ObservableValue
|
|||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import javafx.collections.ObservableMap
|
import javafx.collections.ObservableMap
|
||||||
import org.fxmisc.easybind.EasyBind
|
import org.fxmisc.easybind.EasyBind
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import rx.Observable
|
|
||||||
|
|
||||||
data class GatheredTransactionData(
|
data class GatheredTransactionData(
|
||||||
val transaction: PartiallyResolvedTransaction,
|
val transaction: PartiallyResolvedTransaction,
|
||||||
@ -30,8 +27,7 @@ data class GatheredTransactionData(
|
|||||||
*/
|
*/
|
||||||
data class PartiallyResolvedTransaction(
|
data class PartiallyResolvedTransaction(
|
||||||
val transaction: SignedTransaction,
|
val transaction: SignedTransaction,
|
||||||
val inputs: List<ObservableValue<InputResolution>>
|
val inputs: List<ObservableValue<InputResolution>>) {
|
||||||
) {
|
|
||||||
val id = transaction.id
|
val id = transaction.id
|
||||||
sealed class InputResolution(val stateRef: StateRef) {
|
sealed class InputResolution(val stateRef: StateRef) {
|
||||||
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
|
class Unresolved(stateRef: StateRef) : InputResolution(stateRef)
|
||||||
@ -84,16 +80,15 @@ data class StateMachineData(
|
|||||||
*/
|
*/
|
||||||
class GatheredTransactionDataModel {
|
class GatheredTransactionDataModel {
|
||||||
|
|
||||||
private val transactions: Observable<SignedTransaction> by observable(NodeMonitorModel::transactions)
|
private val transactions by observable(NodeMonitorModel::transactions)
|
||||||
private val stateMachineUpdates: Observable<StateMachineUpdate> by observable(NodeMonitorModel::stateMachineUpdates)
|
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
|
||||||
private val progressTracking: Observable<ProgressTrackingEvent> by observable(NodeMonitorModel::progressTracking)
|
private val progressTracking by observable(NodeMonitorModel::progressTracking)
|
||||||
private val stateMachineTransactionMapping: Observable<StateMachineTransactionMapping> by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
private val stateMachineTransactionMapping by observable(NodeMonitorModel::stateMachineTransactionMapping)
|
||||||
|
|
||||||
val collectedTransactions = transactions.recordInSequence()
|
private val collectedTransactions = transactions.recordInSequence()
|
||||||
val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
private val transactionMap = collectedTransactions.associateBy(SignedTransaction::id)
|
||||||
val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
|
||||||
val stateMachineStatus: ObservableMap<StateMachineRunId, out ObservableValue<StateMachineStatus>> =
|
private val stateMachineStatus = stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
|
||||||
stateMachineUpdates.foldToObservableMap(Unit) { update, _unit, map: ObservableMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>> ->
|
|
||||||
when (update) {
|
when (update) {
|
||||||
is StateMachineUpdate.Added -> {
|
is StateMachineUpdate.Added -> {
|
||||||
val added: SimpleObjectProperty<StateMachineStatus> =
|
val added: SimpleObjectProperty<StateMachineStatus> =
|
||||||
@ -107,21 +102,19 @@ class GatheredTransactionDataModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val stateMachineDataList: ObservableList<StateMachineData> =
|
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
||||||
LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
|
|
||||||
StateMachineData(id, progress.map { it?.let { ProtocolStatus(it.message) } }, status)
|
StateMachineData(id, progress.map { it?.let { ProtocolStatus(it.message) } }, status)
|
||||||
}.getObservableValues()
|
}.getObservableValues()
|
||||||
val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
private val stateMachineDataMap = stateMachineDataList.associateBy(StateMachineData::id)
|
||||||
val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
private val smTxMappingList = stateMachineTransactionMapping.recordInSequence()
|
||||||
val partiallyResolvedTransactions = collectedTransactions.map {
|
private val partiallyResolvedTransactions = collectedTransactions.map {
|
||||||
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
PartiallyResolvedTransaction.fromSignedTransaction(it, transactionMap)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We JOIN the transaction list with state machines
|
* We JOIN the transaction list with state machines
|
||||||
*/
|
*/
|
||||||
val gatheredTransactionDataList: ObservableList<out GatheredTransactionData> =
|
val gatheredTransactionDataList = partiallyResolvedTransactions.leftOuterJoin(
|
||||||
partiallyResolvedTransactions.leftOuterJoin(
|
|
||||||
smTxMappingList,
|
smTxMappingList,
|
||||||
PartiallyResolvedTransaction::id,
|
PartiallyResolvedTransaction::id,
|
||||||
StateMachineTransactionMapping::transactionId
|
StateMachineTransactionMapping::transactionId
|
||||||
|
@ -1,16 +1,18 @@
|
|||||||
package com.r3corda.client.model
|
package com.r3corda.client.model
|
||||||
|
|
||||||
import com.r3corda.client.fxutils.foldToObservableList
|
import com.r3corda.client.fxutils.foldToObservableList
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.services.NetworkMapCache
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
|
import com.r3corda.node.services.network.NetworkMapService
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
import kotlinx.support.jdk8.collections.removeIf
|
import kotlinx.support.jdk8.collections.removeIf
|
||||||
import rx.Observable
|
import java.security.PublicKey
|
||||||
|
|
||||||
class NetworkIdentityModel {
|
class NetworkIdentityModel {
|
||||||
private val networkIdentityObservable: Observable<NetworkMapCache.MapChange> by observable(NodeMonitorModel::networkMap)
|
private val networkIdentityObservable by observable(NodeMonitorModel::networkMap)
|
||||||
|
|
||||||
val networkIdentities: ObservableList<NodeInfo> =
|
private val networkIdentities: ObservableList<NodeInfo> =
|
||||||
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
|
networkIdentityObservable.foldToObservableList(Unit) { update, _accumulator, observableList ->
|
||||||
observableList.removeIf {
|
observableList.removeIf {
|
||||||
when (update.type) {
|
when (update.type) {
|
||||||
@ -21,4 +23,18 @@ class NetworkIdentityModel {
|
|||||||
}
|
}
|
||||||
observableList.addAll(update.node)
|
observableList.addAll(update.node)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private val rpcProxy by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
|
||||||
|
val parties: ObservableList<NodeInfo> = networkIdentities.filtered { !it.isCordaService() }
|
||||||
|
val notaries: ObservableList<NodeInfo> = networkIdentities.filtered { it.advertisedServices.any { it.info.type.isNotary() } }
|
||||||
|
val myIdentity = rpcProxy.map { it?.nodeIdentity() }
|
||||||
|
|
||||||
|
private fun NodeInfo.isCordaService(): Boolean {
|
||||||
|
return advertisedServices.any { it.info.type == NetworkMapService.type || it.info.type.isNotary() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun lookup(publicKey: PublicKey): NodeInfo? {
|
||||||
|
return parties.firstOrNull { it.legalIdentity.owningKey == publicKey } ?: notaries.firstOrNull { it.notaryIdentity.owningKey == publicKey }
|
||||||
|
}
|
||||||
}
|
}
|
@ -1,15 +1,14 @@
|
|||||||
package com.r3corda.client.model
|
package com.r3corda.client.model
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
import com.r3corda.client.CordaRPCClient
|
import com.r3corda.client.CordaRPCClient
|
||||||
import com.r3corda.core.contracts.ClientToServiceCommand
|
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||||
import com.r3corda.core.node.NodeInfo
|
|
||||||
import com.r3corda.core.node.services.NetworkMapCache
|
import com.r3corda.core.node.services.NetworkMapCache
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
||||||
import com.r3corda.core.node.services.Vault
|
import com.r3corda.core.node.services.Vault
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
import com.r3corda.node.services.config.NodeSSLConfiguration
|
import com.r3corda.node.services.config.NodeSSLConfiguration
|
||||||
import com.r3corda.node.services.messaging.ArtemisMessagingComponent.Companion.toHostAndPort
|
|
||||||
import com.r3corda.node.services.messaging.CordaRPCOps
|
import com.r3corda.node.services.messaging.CordaRPCOps
|
||||||
import com.r3corda.node.services.messaging.StateMachineInfo
|
import com.r3corda.node.services.messaging.StateMachineInfo
|
||||||
import com.r3corda.node.services.messaging.StateMachineUpdate
|
import com.r3corda.node.services.messaging.StateMachineUpdate
|
||||||
@ -56,8 +55,8 @@ class NodeMonitorModel {
|
|||||||
* Register for updates to/from a given vault.
|
* Register for updates to/from a given vault.
|
||||||
* TODO provide an unsubscribe mechanism
|
* TODO provide an unsubscribe mechanism
|
||||||
*/
|
*/
|
||||||
fun register(vaultMonitorNodeInfo: NodeInfo, sslConfig: NodeSSLConfiguration, username: String, password: String) {
|
fun register(nodeHostAndPort: HostAndPort, sslConfig: NodeSSLConfiguration, username: String, password: String) {
|
||||||
val client = CordaRPCClient(toHostAndPort(vaultMonitorNodeInfo.address), sslConfig)
|
val client = CordaRPCClient(nodeHostAndPort, sslConfig)
|
||||||
client.start(username, password)
|
client.start(username, password)
|
||||||
val proxy = client.proxy()
|
val proxy = client.proxy()
|
||||||
|
|
||||||
@ -101,7 +100,6 @@ class NodeMonitorModel {
|
|||||||
clientToServiceSource.subscribe {
|
clientToServiceSource.subscribe {
|
||||||
proxy.executeCommand(it)
|
proxy.executeCommand(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
proxyObservable.set(proxy)
|
proxyObservable.set(proxy)
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -25,8 +25,6 @@ apply plugin: 'kotlin'
|
|||||||
apply plugin: 'application'
|
apply plugin: 'application'
|
||||||
|
|
||||||
sourceCompatibility = 1.8
|
sourceCompatibility = 1.8
|
||||||
|
|
||||||
applicationDefaultJvmArgs = ["-javaagent:${rootProject.configurations.quasar.singleFile}"]
|
|
||||||
mainClassName = 'com.r3corda.explorer.Main'
|
mainClassName = 'com.r3corda.explorer.Main'
|
||||||
|
|
||||||
sourceSets {
|
sourceSets {
|
||||||
@ -53,7 +51,7 @@ dependencies {
|
|||||||
testCompile group: 'junit', name: 'junit', version: '4.11'
|
testCompile group: 'junit', name: 'junit', version: '4.11'
|
||||||
|
|
||||||
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
// TornadoFX: A lightweight Kotlin framework for working with JavaFX UI's.
|
||||||
compile 'no.tornado:tornadofx:1.5.1'
|
compile 'no.tornado:tornadofx:1.5.6'
|
||||||
|
|
||||||
// Corda Core: Data structures and basic types needed to work with Corda.
|
// Corda Core: Data structures and basic types needed to work with Corda.
|
||||||
compile project(':core')
|
compile project(':core')
|
||||||
@ -74,4 +72,12 @@ dependencies {
|
|||||||
|
|
||||||
// Humanize: formatting
|
// Humanize: formatting
|
||||||
compile 'com.github.mfornos:humanize-icu:1.2.2'
|
compile 'com.github.mfornos:humanize-icu:1.2.2'
|
||||||
|
|
||||||
|
// Controls FX: more java FX components http://fxexperience.com/controlsfx/
|
||||||
|
compile 'org.controlsfx:controlsfx:8.40.12'
|
||||||
|
}
|
||||||
|
|
||||||
|
task(runDemoNodes, dependsOn: 'classes', type: JavaExec) {
|
||||||
|
main = 'com.r3corda.explorer.MainKt'
|
||||||
|
classpath = sourceSets.main.runtimeClasspath
|
||||||
}
|
}
|
@ -1,60 +1,72 @@
|
|||||||
package com.r3corda.explorer
|
package com.r3corda.explorer
|
||||||
|
|
||||||
|
import com.r3corda.client.mock.EventGenerator
|
||||||
import com.r3corda.client.model.Models
|
import com.r3corda.client.model.Models
|
||||||
import com.r3corda.client.model.NodeMonitorModel
|
import com.r3corda.client.model.NodeMonitorModel
|
||||||
import com.r3corda.core.node.services.ServiceInfo
|
import com.r3corda.core.node.services.ServiceInfo
|
||||||
import com.r3corda.explorer.model.IdentityModel
|
import com.r3corda.explorer.views.runInFxApplicationThread
|
||||||
import com.r3corda.node.driver.PortAllocation
|
import com.r3corda.node.driver.PortAllocation
|
||||||
import com.r3corda.node.driver.driver
|
import com.r3corda.node.driver.driver
|
||||||
import com.r3corda.node.services.config.configureTestSSL
|
import com.r3corda.node.services.config.FullNodeConfiguration
|
||||||
|
import com.r3corda.node.services.messaging.ArtemisMessagingComponent
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
import com.r3corda.node.services.transactions.SimpleNotaryService
|
||||||
import javafx.stage.Stage
|
import javafx.stage.Stage
|
||||||
|
import org.controlsfx.dialog.ExceptionDialog
|
||||||
import tornadofx.App
|
import tornadofx.App
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main class for Explorer, you will need Tornado FX to run the explorer.
|
||||||
|
*/
|
||||||
class Main : App() {
|
class Main : App() {
|
||||||
override val primaryView = MainWindow::class
|
override val primaryView = MainWindow::class
|
||||||
|
|
||||||
override fun start(stage: Stage) {
|
override fun start(stage: Stage) {
|
||||||
|
|
||||||
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
|
||||||
throwable.printStackTrace()
|
throwable.printStackTrace()
|
||||||
System.exit(1)
|
// Show exceptions in exception dialog.
|
||||||
}
|
runInFxApplicationThread {
|
||||||
|
// [showAndWait] need to be in the FX thread
|
||||||
super.start(stage)
|
ExceptionDialog(throwable).showAndWait()
|
||||||
|
System.exit(1)
|
||||||
// start the driver on another thread
|
|
||||||
// TODO Change this to connecting to an actual node (specified on cli/in a config) once we're happy with the code
|
|
||||||
Thread({
|
|
||||||
val portAllocation = PortAllocation.Incremental(20000)
|
|
||||||
driver(portAllocation = portAllocation) {
|
|
||||||
|
|
||||||
val aliceNodeFuture = startNode("Alice")
|
|
||||||
val notaryNodeFuture = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
|
||||||
|
|
||||||
val aliceNode = aliceNodeFuture.get().nodeInfo
|
|
||||||
val notaryNode = notaryNodeFuture.get().nodeInfo
|
|
||||||
|
|
||||||
Models.get<IdentityModel>(Main::class).notary.set(notaryNode.notaryIdentity)
|
|
||||||
Models.get<IdentityModel>(Main::class).myIdentity.set(aliceNode.legalIdentity)
|
|
||||||
Models.get<NodeMonitorModel>(Main::class).register(aliceNode, configureTestSSL(), "user1", "test")
|
|
||||||
|
|
||||||
startNode("Bob").get()
|
|
||||||
|
|
||||||
/* for (i in 0 .. 10000) {
|
|
||||||
Thread.sleep(500)
|
|
||||||
|
|
||||||
val eventGenerator = EventGenerator(
|
|
||||||
parties = listOf(aliceNode.legalIdentity),
|
|
||||||
notary = notaryNode.notaryIdentity
|
|
||||||
)
|
|
||||||
|
|
||||||
eventGenerator.clientToServiceCommandGenerator.map { command ->
|
|
||||||
aliceOutStream.onNext(command)
|
|
||||||
}.generate(Random())
|
|
||||||
}*/
|
|
||||||
waitForAllNodesToFinish()
|
|
||||||
}
|
}
|
||||||
}).start()
|
}
|
||||||
|
super.start(stage)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This main method will starts 3 nodes (Notary, Alice and Bob) locally for UI testing, they will be on localhost:20002, 20004, 20006 respectively.
|
||||||
|
*/
|
||||||
|
fun main(args: Array<String>) {
|
||||||
|
val portAllocation = PortAllocation.Incremental(20000)
|
||||||
|
driver(portAllocation = portAllocation) {
|
||||||
|
val notary = startNode("Notary", advertisedServices = setOf(ServiceInfo(SimpleNotaryService.type)))
|
||||||
|
val alice = startNode("Alice")
|
||||||
|
val bob = startNode("Bob")
|
||||||
|
|
||||||
|
val notaryNode = notary.get()
|
||||||
|
val aliceNode = alice.get()
|
||||||
|
val bobNode = bob.get()
|
||||||
|
|
||||||
|
arrayOf(notaryNode, aliceNode, bobNode).forEach {
|
||||||
|
println("${it.nodeInfo.legalIdentity} started on ${ArtemisMessagingComponent.toHostAndPort(it.nodeInfo.address)}")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register with alice to use alice's RPC proxy to create random events.
|
||||||
|
Models.get<NodeMonitorModel>(Main::class).register(ArtemisMessagingComponent.toHostAndPort(aliceNode.nodeInfo.address), FullNodeConfiguration(aliceNode.config), "user1", "test")
|
||||||
|
val rpcProxy = Models.get<NodeMonitorModel>(Main::class).proxyObservable.get()
|
||||||
|
|
||||||
|
for (i in 0..10000) {
|
||||||
|
Thread.sleep(500)
|
||||||
|
val eventGenerator = EventGenerator(
|
||||||
|
parties = listOf(aliceNode.nodeInfo.legalIdentity, bobNode.nodeInfo.legalIdentity),
|
||||||
|
notary = notaryNode.nodeInfo.notaryIdentity
|
||||||
|
)
|
||||||
|
eventGenerator.clientToServiceCommandGenerator.map { command ->
|
||||||
|
rpcProxy?.executeCommand(command)
|
||||||
|
}.generate(Random())
|
||||||
|
}
|
||||||
|
waitForAllNodesToFinish()
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,9 +1,14 @@
|
|||||||
package com.r3corda.explorer
|
package com.r3corda.explorer
|
||||||
|
|
||||||
|
import com.r3corda.client.model.Models
|
||||||
|
import com.r3corda.client.model.NodeMonitorModel
|
||||||
|
import com.r3corda.explorer.views.LoginView
|
||||||
import com.r3corda.explorer.views.TopLevel
|
import com.r3corda.explorer.views.TopLevel
|
||||||
|
import com.r3corda.node.services.config.configureTestSSL
|
||||||
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory
|
import de.jensd.fx.glyphs.fontawesome.utils.FontAwesomeIconFactory
|
||||||
import jfxtras.resources.JFXtrasFontRoboto
|
import jfxtras.resources.JFXtrasFontRoboto
|
||||||
import tornadofx.*
|
import tornadofx.View
|
||||||
|
import tornadofx.importStylesheet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The root view embeds the [Shell] and provides support for the status bar, and modal dialogs.
|
* The root view embeds the [Shell] and provides support for the status bar, and modal dialogs.
|
||||||
@ -11,10 +16,14 @@ import tornadofx.*
|
|||||||
class MainWindow : View() {
|
class MainWindow : View() {
|
||||||
private val toplevel: TopLevel by inject()
|
private val toplevel: TopLevel by inject()
|
||||||
override val root = toplevel.root
|
override val root = toplevel.root
|
||||||
|
private val loginView by inject<LoginView>()
|
||||||
|
|
||||||
init {
|
init {
|
||||||
// Do this first before creating the notification bar, so it can autosize itself properly.
|
// Do this first before creating the notification bar, so it can autosize itself properly.
|
||||||
loadFontsAndStyles()
|
loadFontsAndStyles()
|
||||||
|
loginView.login { hostAndPort, username, password ->
|
||||||
|
Models.get<NodeMonitorModel>(MainWindow::class).register(hostAndPort, configureTestSSL(), username, password)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadFontsAndStyles() {
|
private fun loadFontsAndStyles() {
|
||||||
|
@ -1,44 +0,0 @@
|
|||||||
package com.r3corda.explorer.components
|
|
||||||
|
|
||||||
import javafx.scene.control.Alert
|
|
||||||
import javafx.scene.control.Label
|
|
||||||
import javafx.scene.control.TextArea
|
|
||||||
import javafx.scene.layout.GridPane
|
|
||||||
import javafx.scene.layout.Priority
|
|
||||||
import java.io.PrintWriter
|
|
||||||
import java.io.StringWriter
|
|
||||||
|
|
||||||
class ExceptionDialog(ex: Throwable) : Alert(AlertType.ERROR) {
|
|
||||||
|
|
||||||
private fun Throwable.toExceptionText(): String {
|
|
||||||
return StringWriter().use {
|
|
||||||
PrintWriter(it).use {
|
|
||||||
this.printStackTrace(it)
|
|
||||||
}
|
|
||||||
it.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Create expandable Exception.
|
|
||||||
val label = Label("The exception stacktrace was:")
|
|
||||||
contentText = ex.message
|
|
||||||
|
|
||||||
val textArea = TextArea(ex.toExceptionText())
|
|
||||||
textArea.isEditable = false
|
|
||||||
textArea.isWrapText = true
|
|
||||||
|
|
||||||
textArea.maxWidth = Double.MAX_VALUE
|
|
||||||
textArea.maxHeight = Double.MAX_VALUE
|
|
||||||
GridPane.setVgrow(textArea, Priority.ALWAYS)
|
|
||||||
GridPane.setHgrow(textArea, Priority.ALWAYS)
|
|
||||||
|
|
||||||
val expContent = GridPane()
|
|
||||||
expContent.maxWidth = Double.MAX_VALUE
|
|
||||||
expContent.add(label, 0, 0)
|
|
||||||
expContent.add(textArea, 0, 1)
|
|
||||||
|
|
||||||
// Set expandable Exception into the dialog pane.
|
|
||||||
dialogPane.expandableContent = expContent
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,194 @@
|
|||||||
|
package com.r3corda.explorer.identicon
|
||||||
|
|
||||||
|
import com.google.common.base.Splitter
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
|
import javafx.scene.SnapshotParameters
|
||||||
|
import javafx.scene.canvas.Canvas
|
||||||
|
import javafx.scene.canvas.GraphicsContext
|
||||||
|
import javafx.scene.control.ContentDisplay
|
||||||
|
import javafx.scene.control.Tooltip
|
||||||
|
import javafx.scene.image.ImageView
|
||||||
|
import javafx.scene.image.WritableImage
|
||||||
|
import javafx.scene.paint.Color
|
||||||
|
import javafx.scene.text.TextAlignment
|
||||||
|
|
||||||
|
/**
|
||||||
|
* (The MIT License)
|
||||||
|
* Copyright (c) 2007-2012 Don Park <donpark@docuverse.com>
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
* a copy of this software and associated documentation files (the
|
||||||
|
* 'Software'), to deal in the Software without restriction, including
|
||||||
|
* without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
* distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
* permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
* the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be
|
||||||
|
* included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
||||||
|
* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
||||||
|
* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
||||||
|
* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* The code originated from : https://github.com/donpark/identicon
|
||||||
|
* And has been modified to Kotlin and JavaFX instead of Java code using AWT
|
||||||
|
*/
|
||||||
|
|
||||||
|
class IdenticonRenderer {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
/**
|
||||||
|
* Each patch is a polygon created from a list of vertices on a 5 by 5 grid.
|
||||||
|
* Vertices are numbered from 0 to 24, starting from top-left corner of the
|
||||||
|
* grid, moving left to right and top to bottom.
|
||||||
|
*/
|
||||||
|
private val patchTypes = arrayOf(
|
||||||
|
byteArrayOf(0, 4, 24, 20, 0),
|
||||||
|
byteArrayOf(0, 4, 20, 0),
|
||||||
|
byteArrayOf(2, 24, 20, 2),
|
||||||
|
byteArrayOf(0, 2, 20, 22, 0),
|
||||||
|
byteArrayOf(2, 14, 22, 10, 2),
|
||||||
|
byteArrayOf(0, 14, 24, 22, 0),
|
||||||
|
byteArrayOf(2, 24, 22, 13, 11, 22, 20, 2),
|
||||||
|
byteArrayOf(0, 14, 22, 0),
|
||||||
|
byteArrayOf(6, 8, 18, 16, 6),
|
||||||
|
byteArrayOf(4, 20, 10, 12, 2, 4),
|
||||||
|
byteArrayOf(0, 2, 12, 10, 0),
|
||||||
|
byteArrayOf(10, 14, 22, 10),
|
||||||
|
byteArrayOf(20, 12, 24, 20),
|
||||||
|
byteArrayOf(10, 2, 12, 10),
|
||||||
|
byteArrayOf(0, 2, 10, 0),
|
||||||
|
byteArrayOf(0, 4, 24, 20, 0)).map(::Patch)
|
||||||
|
|
||||||
|
private val PATCH_CELLS = 4
|
||||||
|
private val PATCH_GRIDS = PATCH_CELLS + 1
|
||||||
|
private val PATCH_SYMMETRIC: Byte = 1
|
||||||
|
private val PATCH_INVERTED: Byte = 2
|
||||||
|
|
||||||
|
private val patchFlags = byteArrayOf(PATCH_SYMMETRIC, 0, 0, 0, PATCH_SYMMETRIC, 0, 0, 0, PATCH_SYMMETRIC, 0, 0, 0, 0, 0, 0, (PATCH_SYMMETRIC + PATCH_INVERTED).toByte())
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Patch(private val byteArray: ByteArray) {
|
||||||
|
fun x(patchSize: Double): DoubleArray {
|
||||||
|
return byteArray.map(Byte::toInt).map { it % PATCH_GRIDS * (patchSize / PATCH_CELLS) - patchSize / 2 }.toDoubleArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun y(patchSize: Double): DoubleArray {
|
||||||
|
return byteArray.map(Byte::toInt).map { it / PATCH_GRIDS * (patchSize / PATCH_CELLS) - patchSize / 2 }.toDoubleArray()
|
||||||
|
}
|
||||||
|
|
||||||
|
val size = byteArray.size
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns rendered identicon image for given identicon code.
|
||||||
|
* Size of the returned identicon image is determined by patchSize set using
|
||||||
|
* [setPatchSize]. Since a 9-block identicon consists of 3x3 patches,
|
||||||
|
* width and height will be 3 times the patch size.
|
||||||
|
*/
|
||||||
|
fun render(code: Int, patchSize: Double, backgroundColor: Color = Color.WHITE): WritableImage {
|
||||||
|
// decode the code into parts
|
||||||
|
val middleType = intArrayOf(0, 4, 8, 15)[code and 0x3] // bit 0-1: middle patch type
|
||||||
|
val middleInvert = code shr 2 and 0x1 != 0 // bit 2: middle invert
|
||||||
|
val cornerType = code shr 3 and 0x0f // bit 3-6: corner patch type
|
||||||
|
val cornerInvert = code shr 7 and 0x1 != 0 // bit 7: corner invert
|
||||||
|
val cornerTurn = code shr 8 and 0x3 // bit 8-9: corner turns
|
||||||
|
val sideType = code shr 10 and 0x0f // bit 10-13: side patch type
|
||||||
|
val sideInvert = code shr 14 and 0x1 != 0 // bit 14: side invert
|
||||||
|
val sideTurn = code shr 15 and 0x3 // bit 15: corner turns
|
||||||
|
val blue = code shr 16 and 0x01f // bit 16-20: blue color component
|
||||||
|
val green = code shr 21 and 0x01f // bit 21-26: green color component
|
||||||
|
val red = code shr 27 and 0x01f // bit 27-31: red color component
|
||||||
|
|
||||||
|
// color components are used at top of the range for color difference
|
||||||
|
// use white background for now.
|
||||||
|
// TODO: support transparency.
|
||||||
|
val fillColor = Color.rgb(red shl 3, green shl 3, blue shl 3)
|
||||||
|
// outline shapes with a noticeable color (complementary will do) if
|
||||||
|
// shape color and background color are too similar (measured by color
|
||||||
|
// distance).
|
||||||
|
val strokeColor = if (getColorDistance(fillColor, backgroundColor) < 32.0f) fillColor.invert() else null
|
||||||
|
|
||||||
|
val sourceSize = patchSize * 3
|
||||||
|
val canvas = Canvas(sourceSize, sourceSize)
|
||||||
|
val g = canvas.graphicsContext2D
|
||||||
|
/** Rendering Order:
|
||||||
|
* 6 2 7
|
||||||
|
* 5 1 3
|
||||||
|
* 9 4 8 */
|
||||||
|
val color = PatchColor(fillColor, strokeColor, backgroundColor)
|
||||||
|
drawPatch(g, patchSize, patchSize, middleType, 0, patchSize, middleInvert, color)
|
||||||
|
drawPatch(g, patchSize, 0.0, sideType, sideTurn, patchSize, sideInvert, color)
|
||||||
|
drawPatch(g, patchSize * 2, patchSize, sideType, sideTurn + 1, patchSize, sideInvert, color)
|
||||||
|
drawPatch(g, patchSize, patchSize * 2, sideType, sideTurn + 2, patchSize, sideInvert, color)
|
||||||
|
drawPatch(g, 0.0, patchSize, sideType, sideTurn + 3, patchSize, sideInvert, color)
|
||||||
|
drawPatch(g, 0.0, 0.0, cornerType, cornerTurn, patchSize, cornerInvert, color)
|
||||||
|
drawPatch(g, patchSize * 2, 0.0, cornerType, cornerTurn + 1, patchSize, cornerInvert, color)
|
||||||
|
drawPatch(g, patchSize * 2, patchSize * 2, cornerType, cornerTurn + 2, patchSize, cornerInvert, color)
|
||||||
|
drawPatch(g, 0.0, patchSize * 2, cornerType, cornerTurn + 3, patchSize, cornerInvert, color)
|
||||||
|
return canvas.snapshot(SnapshotParameters(), WritableImage(sourceSize.toInt(), sourceSize.toInt()))
|
||||||
|
}
|
||||||
|
|
||||||
|
private class PatchColor(private val fillColor: Color, val strokeColor: Color?, private val backgroundColor: Color) {
|
||||||
|
fun background(invert: Boolean) = if (invert) fillColor else backgroundColor
|
||||||
|
fun fill(invert: Boolean) = if (invert) backgroundColor else fillColor
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun drawPatch(g: GraphicsContext, x: Double, y: Double, patchIndex: Int, turn: Int, patchSize: Double, _invert: Boolean, color: PatchColor) {
|
||||||
|
val patch = patchTypes[patchIndex % patchTypes.size]
|
||||||
|
val invert = if ((patchFlags[patchIndex].toInt() and PATCH_INVERTED.toInt()) !== 0) !_invert else _invert
|
||||||
|
g.apply {
|
||||||
|
// paint background
|
||||||
|
clearRect(x, y, patchSize, patchSize)
|
||||||
|
fill = color.background(invert)
|
||||||
|
stroke = color.background(invert)
|
||||||
|
fillRect(x, y, patchSize, patchSize)
|
||||||
|
strokeRect(x, y, patchSize, patchSize)
|
||||||
|
// offset and rotate coordinate space by patch position (x, y) and
|
||||||
|
// 'turn' before rendering patch shape
|
||||||
|
val saved = transform
|
||||||
|
translate(x + patchSize / 2, y + patchSize / 2)
|
||||||
|
rotate((turn % 4 * 90).toDouble())
|
||||||
|
|
||||||
|
// if stroke color was specified, apply stroke
|
||||||
|
// stroke color should be specified if fore color is too close to the
|
||||||
|
// back color.
|
||||||
|
if (color.strokeColor != null) {
|
||||||
|
stroke = color.strokeColor
|
||||||
|
strokePolygon(patch.x(patchSize), patch.y(patchSize), patch.size)
|
||||||
|
}
|
||||||
|
// render rotated patch using fore color (back color if inverted)
|
||||||
|
fill = color.fill(invert)
|
||||||
|
fillPolygon(patch.x(patchSize), patch.y(patchSize), patch.size)
|
||||||
|
// restore rotation
|
||||||
|
transform = saved
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns distance between two colors.
|
||||||
|
*/
|
||||||
|
private fun getColorDistance(c1: Color, c2: Color): Float {
|
||||||
|
val dx = (c1.red - c2.red) * 256
|
||||||
|
val dy = (c1.green - c2.green) * 256
|
||||||
|
val dz = (c1.blue - c2.blue) * 256
|
||||||
|
return Math.sqrt(dx * dx + dy * dy + dz * dz.toDouble()).toFloat()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun identicon(secureHash: SecureHash, size: Double): WritableImage {
|
||||||
|
return IdenticonRenderer().render(secureHash.hashCode(), size)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun identiconToolTip(secureHash: SecureHash): Tooltip {
|
||||||
|
return Tooltip(Splitter.fixedLength(16).split("$secureHash").joinToString("\n")).apply {
|
||||||
|
contentDisplay = ContentDisplay.TOP
|
||||||
|
textAlignment = TextAlignment.CENTER
|
||||||
|
graphic = ImageView(identicon(secureHash, 30.0))
|
||||||
|
}
|
||||||
|
}
|
@ -1,9 +0,0 @@
|
|||||||
package com.r3corda.explorer.model
|
|
||||||
|
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
|
||||||
|
|
||||||
class IdentityModel {
|
|
||||||
val myIdentity = SimpleObjectProperty<Party?>()
|
|
||||||
val notary = SimpleObjectProperty<Party?>()
|
|
||||||
}
|
|
@ -1,12 +1,22 @@
|
|||||||
package com.r3corda.explorer.model
|
package com.r3corda.explorer.model
|
||||||
|
|
||||||
import javafx.beans.property.SimpleObjectProperty
|
import javafx.beans.property.SimpleObjectProperty
|
||||||
|
import javafx.scene.image.Image
|
||||||
|
|
||||||
enum class SelectedView {
|
enum class SelectedView(val displayableName: String, val image: Image, val subviews: Array<SelectedView> = emptyArray()) {
|
||||||
Home,
|
Home("Home", getImage("home.png")),
|
||||||
Cash,
|
Transaction("Transaction", getImage("tx.png")),
|
||||||
Transaction,
|
Setting("Setting", getImage("settings_lrg.png")),
|
||||||
NewTransaction
|
NewTransaction("New Transaction", getImage("cash.png")),
|
||||||
|
Cash("Cash", getImage("cash.png"), arrayOf(Transaction, NewTransaction)),
|
||||||
|
NetworkMap("Network Map", getImage("cash.png")),
|
||||||
|
Vault("Vault", getImage("cash.png"), arrayOf(Cash)),
|
||||||
|
Network("Network", getImage("inst.png"), arrayOf(NetworkMap, Transaction))
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getImage(imageName: String): Image {
|
||||||
|
val basePath = "/com/r3corda/explorer/images"
|
||||||
|
return Image("$basePath/$imageName")
|
||||||
}
|
}
|
||||||
|
|
||||||
class TopLevelModel {
|
class TopLevelModel {
|
||||||
|
@ -1,212 +1,142 @@
|
|||||||
package com.r3corda.explorer.views
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
import com.r3corda.client.fxutils.*
|
import com.r3corda.client.fxutils.*
|
||||||
import com.r3corda.client.model.ContractStateModel
|
import com.r3corda.client.model.*
|
||||||
import com.r3corda.client.model.observableList
|
|
||||||
import com.r3corda.client.model.observableValue
|
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.core.contracts.Amount
|
import com.r3corda.core.contracts.Amount
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
import com.r3corda.core.contracts.withoutIssuer
|
import com.r3corda.core.contracts.withoutIssuer
|
||||||
import com.r3corda.core.crypto.Party
|
import com.r3corda.core.crypto.Party
|
||||||
import com.r3corda.explorer.formatters.AmountFormatter
|
import com.r3corda.explorer.formatters.AmountFormatter
|
||||||
|
import com.r3corda.explorer.identicon.identicon
|
||||||
|
import com.r3corda.explorer.identicon.identiconToolTip
|
||||||
import com.r3corda.explorer.model.ReportingCurrencyModel
|
import com.r3corda.explorer.model.ReportingCurrencyModel
|
||||||
import com.r3corda.explorer.model.SettingsModel
|
import com.r3corda.explorer.model.SettingsModel
|
||||||
import com.r3corda.explorer.ui.*
|
import com.r3corda.explorer.ui.*
|
||||||
|
import com.sun.javafx.collections.ObservableListWrapper
|
||||||
|
import javafx.application.Platform
|
||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.geometry.Insets
|
||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.chart.NumberAxis
|
||||||
import javafx.scene.control.*
|
import javafx.scene.control.*
|
||||||
import javafx.scene.image.ImageView
|
import javafx.scene.image.ImageView
|
||||||
import javafx.scene.input.MouseButton
|
import javafx.scene.layout.BorderPane
|
||||||
import javafx.scene.input.MouseEvent
|
|
||||||
import javafx.scene.layout.HBox
|
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.layout.VBox
|
||||||
import org.fxmisc.easybind.EasyBind
|
import org.fxmisc.easybind.EasyBind
|
||||||
import tornadofx.UIComponent
|
import tornadofx.*
|
||||||
import tornadofx.View
|
import java.time.Instant
|
||||||
import java.time.LocalDateTime
|
import java.time.LocalDateTime
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
sealed class FilterCriteria {
|
class CashViewer : View(), CordaView {
|
||||||
abstract fun matches(string: String): Boolean
|
// Inject UI elements.
|
||||||
|
override val root: BorderPane by fxml()
|
||||||
|
|
||||||
object All : FilterCriteria() {
|
// View's widget.
|
||||||
override fun matches(string: String) = true
|
override val viewName = "Cash"
|
||||||
|
override val widget: Node = vbox {
|
||||||
|
padding = Insets(0.0, 10.0, 0.0, 0.0)
|
||||||
|
val xAxis = NumberAxis().apply {
|
||||||
|
//isAutoRanging = true
|
||||||
|
isMinorTickVisible = false
|
||||||
|
isForceZeroInRange = false
|
||||||
|
tickLabelFormatter = stringConverter {
|
||||||
|
Instant.ofEpochMilli(it.toLong()).atZone(TimeZone.getDefault().toZoneId()).toLocalTime().toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
val yAxis = NumberAxis().apply {
|
||||||
|
isAutoRanging = true
|
||||||
|
isMinorTickVisible = false
|
||||||
|
isForceZeroInRange = false
|
||||||
|
tickLabelFormatter = stringConverter { it.toStringWithSuffix(0) }
|
||||||
|
}
|
||||||
|
linechart(null, xAxis, yAxis) {
|
||||||
|
series("USD") {
|
||||||
|
runAsync {
|
||||||
|
while (true) {
|
||||||
|
Thread.sleep(1000)
|
||||||
|
Platform.runLater {
|
||||||
|
// Modify data in UI thread.
|
||||||
|
if (data.size > 300) data.remove(0, 1)
|
||||||
|
data(System.currentTimeMillis(), sumAmount.value.quantity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
createSymbols = false
|
||||||
|
animated = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FilterString(val filterString: String) : FilterCriteria() {
|
|
||||||
override fun matches(string: String) = string.contains(filterString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CashViewer : View() {
|
|
||||||
// Inject UI elements
|
|
||||||
override val root: SplitPane by fxml()
|
|
||||||
|
|
||||||
val topSplitPane: SplitPane by fxid()
|
|
||||||
// Left pane
|
// Left pane
|
||||||
val leftPane: VBox by fxid()
|
private val leftPane: VBox by fxid()
|
||||||
val searchCriteriaTextField: TextField by fxid()
|
private val splitPane: SplitPane by fxid()
|
||||||
val searchCancelImageView: ImageView by fxid()
|
private val totalMatchingLabel: Label by fxid()
|
||||||
val totalMatchingLabel: Label by fxid()
|
private val cashViewerTable: TreeTableView<ViewerNode> by fxid()
|
||||||
val cashViewerTable: TreeTableView<ViewerNode> by fxid()
|
private val cashViewerTableIssuerCurrency: TreeTableColumn<ViewerNode, String> by fxid()
|
||||||
val cashViewerTableIssuerCurrency: TreeTableColumn<ViewerNode, String> by fxid()
|
private val cashViewerTableLocalCurrency: TreeTableColumn<ViewerNode, Amount<Currency>?> by fxid()
|
||||||
val cashViewerTableLocalCurrency: TreeTableColumn<ViewerNode, Amount<Currency>?> by fxid()
|
private val cashViewerTableEquiv: TreeTableColumn<ViewerNode, Amount<Currency>?> by fxid()
|
||||||
val cashViewerTableEquiv: TreeTableColumn<ViewerNode, Amount<Currency>?> by fxid()
|
|
||||||
|
|
||||||
// Right pane
|
// Right pane
|
||||||
val rightPane: VBox by fxid()
|
private val rightPane: VBox by fxid()
|
||||||
val totalPositionsLabel: Label by fxid()
|
private val totalPositionsLabel: Label by fxid()
|
||||||
val equivSumLabel: Label by fxid()
|
private val cashStatesList: ListView<StateRow> by fxid()
|
||||||
val cashStatesList: ListView<StateRow> by fxid()
|
private val toggleButton by fxid<Button>()
|
||||||
|
|
||||||
// Inject observables
|
// Inject observables
|
||||||
val cashStates by observableList(ContractStateModel::cashStates)
|
private val cashStates by observableList(ContractStateModel::cashStates)
|
||||||
val reportingCurrency: ObservableValue<Currency> by observableValue(SettingsModel::reportingCurrency)
|
private val reportingCurrency by observableValue(SettingsModel::reportingCurrency)
|
||||||
val reportingExchange: ObservableValue<Pair<Currency, (Amount<Currency>) -> Amount<Currency>>>
|
private val reportingExchange by observableValue(ReportingCurrencyModel::reportingExchange)
|
||||||
by observableValue(ReportingCurrencyModel::reportingExchange)
|
private val exchangeRate: ObservableValue<ExchangeRate> by observableValue(ExchangeRateModel::exchangeRate)
|
||||||
|
private val sumAmount = AmountBindings.sumAmountExchange(
|
||||||
|
cashStates.map { it.state.data.amount.withoutIssuer() },
|
||||||
|
reportingCurrency,
|
||||||
|
exchangeRate
|
||||||
|
)
|
||||||
|
|
||||||
|
private val selectedNode = cashViewerTable.singleRowSelection().map {
|
||||||
|
when (it) {
|
||||||
|
is SingleRowSelection.Selected -> it.node
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private val view = ChosenList(selectedNode.map {
|
||||||
|
when (it) {
|
||||||
|
null -> FXCollections.observableArrayList(leftPane)
|
||||||
|
else -> FXCollections.observableArrayList(leftPane, rightPane)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This holds the data for each row in the TreeTable.
|
* This holds the data for each row in the TreeTable.
|
||||||
*/
|
*/
|
||||||
sealed class ViewerNode {
|
sealed class ViewerNode(val equivAmount: ObservableValue<out Amount<Currency>>,
|
||||||
object Root : ViewerNode()
|
val states: ObservableList<StateAndRef<Cash.State>>) {
|
||||||
class IssuerNode(
|
class IssuerNode(val issuer: Party,
|
||||||
val issuer: Party,
|
sumEquivAmount: ObservableValue<out Amount<Currency>>,
|
||||||
val sumEquivAmount: ObservableValue<out Amount<Currency>>,
|
states: ObservableList<StateAndRef<Cash.State>>) : ViewerNode(sumEquivAmount, states)
|
||||||
val states: ObservableList<StateAndRef<Cash.State>>
|
|
||||||
) : ViewerNode()
|
class CurrencyNode(val amount: ObservableValue<Amount<Currency>>,
|
||||||
class CurrencyNode(
|
equivAmount: ObservableValue<Amount<Currency>>,
|
||||||
val amount: ObservableValue<Amount<Currency>>,
|
states: ObservableList<StateAndRef<Cash.State>>) : ViewerNode(equivAmount, states)
|
||||||
val equivAmount: ObservableValue<Amount<Currency>>,
|
|
||||||
val states: ObservableList<StateAndRef<Cash.State>>
|
|
||||||
) : ViewerNode()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* We allow filtering by both issuer and currency. We do this by filtering by both at the same time and picking the
|
|
||||||
* one which produces more results, which seems to work, as the set of currency strings don't really overlap with
|
|
||||||
* issuer strings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Holds the filtering criterion based on the input text
|
|
||||||
*/
|
|
||||||
private val filterCriteria = searchCriteriaTextField.textProperty().map { text ->
|
|
||||||
if (text.isBlank()) {
|
|
||||||
FilterCriteria.All
|
|
||||||
} else {
|
|
||||||
FilterCriteria.FilterString(text)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Filter cash states based on issuer.
|
|
||||||
*/
|
|
||||||
private val issueFilteredCashStates = cashStates.filter(filterCriteria.map { criteria ->
|
|
||||||
{ state: StateAndRef<Cash.State> ->
|
|
||||||
criteria.matches(state.state.data.amount.token.issuer.party.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
/**
|
|
||||||
* Now filter cash states based on currency.
|
|
||||||
*/
|
|
||||||
private val currencyFilteredCashStates = cashStates.filter(filterCriteria.map { criteria ->
|
|
||||||
{ state: StateAndRef<Cash.State> ->
|
|
||||||
criteria.matches(state.state.data.amount.token.product.toString())
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Now we pick which one to use.
|
|
||||||
*/
|
|
||||||
private val filteredCashStates = ChosenList(filterCriteria.map {
|
|
||||||
if (issueFilteredCashStates.size > currencyFilteredCashStates.size) {
|
|
||||||
issueFilteredCashStates
|
|
||||||
} else {
|
|
||||||
currencyFilteredCashStates
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This is where we aggregate the list of cash states into the TreeTable structure.
|
|
||||||
*/
|
|
||||||
val cashViewerIssueNodes: ObservableList<TreeItem<out ViewerNode.IssuerNode>> =
|
|
||||||
/**
|
|
||||||
* First we group the states based on the issuer. [memberStates] is all states holding currency issued by [issuer]
|
|
||||||
*/
|
|
||||||
AggregatedList(filteredCashStates, { it.state.data.amount.token.issuer.party }) { issuer, memberStates ->
|
|
||||||
/**
|
|
||||||
* Next we create subgroups based on currency. [memberStates] here is all states holding currency [currency] issued by [issuer] above.
|
|
||||||
* Note that these states will not be displayed in the TreeTable, but rather in the side pane if the user clicks on the row.
|
|
||||||
*/
|
|
||||||
val currencyNodes = AggregatedList(memberStates, { it.state.data.amount.token.product }) { currency, memberStates ->
|
|
||||||
/**
|
|
||||||
* We sum the states in the subgroup, to be displayed in the "Local Currency" column
|
|
||||||
*/
|
|
||||||
val amounts = memberStates.map { it.state.data.amount.withoutIssuer() }
|
|
||||||
val sumAmount = amounts.foldObservable(Amount(0, currency), Amount<Currency>::plus)
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We exchange the sum to the reporting currency, to be displayed in the "<currency> Equiv" column.
|
|
||||||
*/
|
|
||||||
val equivSumAmount = EasyBind.combine(sumAmount, reportingExchange) { sum, exchange ->
|
|
||||||
exchange.second(sum)
|
|
||||||
}
|
|
||||||
/**
|
|
||||||
* Finally assemble the actual TreeTable Currency node.
|
|
||||||
*/
|
|
||||||
TreeItem(ViewerNode.CurrencyNode(sumAmount, equivSumAmount, memberStates))
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Now that we have all nodes per currency, we sum the exchanged amounts, to be displayed in the
|
|
||||||
* "<currency> Equiv" column, this time on the issuer level.
|
|
||||||
*/
|
|
||||||
val equivAmounts = currencyNodes.map { it.value.equivAmount }.flatten()
|
|
||||||
val equivSumAmount = reportingCurrency.bind { currency ->
|
|
||||||
equivAmounts.foldObservable(Amount(0, currency), Amount<Currency>::plus)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Assemble the Issuer node.
|
|
||||||
*/
|
|
||||||
val treeItem = TreeItem(ViewerNode.IssuerNode(issuer, equivSumAmount, memberStates))
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bind the children in the TreeTable structure.
|
|
||||||
*
|
|
||||||
* TODO Perhaps we shouldn't do this here, but rather have a generic way of binding nodes to the treetable once.
|
|
||||||
*/
|
|
||||||
treeItem.isExpanded = true
|
|
||||||
val children: List<TreeItem<out ViewerNode.IssuerNode>> = treeItem.children
|
|
||||||
Bindings.bindContent(children, currencyNodes)
|
|
||||||
treeItem
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Now we build up the Observables needed for the side pane, given that the user clicks on a row.
|
|
||||||
*/
|
|
||||||
val selectedViewerNode = cashViewerTable.singleRowSelection()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Holds data for a single state, to be displayed in the list in the side pane.
|
* Holds data for a single state, to be displayed in the list in the side pane.
|
||||||
*/
|
*/
|
||||||
data class StateRow (
|
data class StateRow(val originated: LocalDateTime, val stateAndRef: StateAndRef<Cash.State>)
|
||||||
val originated: LocalDateTime,
|
|
||||||
val stateAndRef: StateAndRef<Cash.State>
|
|
||||||
)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A small class describing the graphics of a single state.
|
* A small class describing the graphics of a single state.
|
||||||
*/
|
*/
|
||||||
inner class StateRowGraphic(
|
inner class StateRowGraphic(val stateRow: StateRow) : UIComponent() {
|
||||||
val stateRow: StateRow
|
override val root: Parent by fxml("CashStateViewer.fxml")
|
||||||
) : UIComponent() {
|
|
||||||
override val root: HBox by fxml("CashStateViewer.fxml")
|
|
||||||
|
|
||||||
val equivLabel: Label by fxid()
|
val equivLabel: Label by fxid()
|
||||||
val stateIdValueLabel: Label by fxid()
|
val stateIdValueLabel: Label by fxid()
|
||||||
@ -224,8 +154,12 @@ class CashViewer : View() {
|
|||||||
val amountFormatter = AmountFormatter.boring
|
val amountFormatter = AmountFormatter.boring
|
||||||
val equivFormatter = AmountFormatter.boring
|
val equivFormatter = AmountFormatter.boring
|
||||||
|
|
||||||
|
stateIdValueLabel.apply {
|
||||||
|
text = stateRow.stateAndRef.ref.toString().substring(0, 16) + "...[${stateRow.stateAndRef.ref.index}]"
|
||||||
|
graphic = ImageView(identicon(stateRow.stateAndRef.ref.txhash, 10.0))
|
||||||
|
tooltip = identiconToolTip(stateRow.stateAndRef.ref.txhash)
|
||||||
|
}
|
||||||
equivLabel.textProperty().bind(equivAmount.map { it.token.currencyCode.toString() })
|
equivLabel.textProperty().bind(equivAmount.map { it.token.currencyCode.toString() })
|
||||||
stateIdValueLabel.text = stateRow.stateAndRef.ref.toString()
|
|
||||||
issuerValueLabel.text = stateRow.stateAndRef.state.data.amount.token.issuer.toString()
|
issuerValueLabel.text = stateRow.stateAndRef.state.data.amount.token.issuer.toString()
|
||||||
originatedValueLabel.text = stateRow.originated.toString()
|
originatedValueLabel.text = stateRow.originated.toString()
|
||||||
amountValueLabel.text = amountFormatter.format(amountNoIssuer)
|
amountValueLabel.text = amountFormatter.format(amountNoIssuer)
|
||||||
@ -233,132 +167,140 @@ class CashViewer : View() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The list of states related to the current selection. If none or the root is selected it's empty, if an issuer or
|
|
||||||
* currency node is selected it's the relevant states.
|
|
||||||
*/
|
|
||||||
private val noSelectionStates = FXCollections.observableArrayList<StateAndRef<Cash.State>>()
|
|
||||||
private val selectedViewerNodeStates = ChosenList(selectedViewerNode.map { selection ->
|
|
||||||
when (selection) {
|
|
||||||
is SingleRowSelection.None -> noSelectionStates
|
|
||||||
is SingleRowSelection.Selected ->
|
|
||||||
when (selection.node) {
|
|
||||||
CashViewer.ViewerNode.Root -> noSelectionStates
|
|
||||||
is CashViewer.ViewerNode.IssuerNode -> selection.node.states
|
|
||||||
is CashViewer.ViewerNode.CurrencyNode -> selection.node.states
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We re-display the exchanged sum amount, if we have a selection.
|
|
||||||
*/
|
|
||||||
private val noSelectionSumEquiv = reportingCurrency.map { Amount(0, it) }
|
|
||||||
private val selectedViewerNodeSumEquiv = selectedViewerNode.bindOut { selection ->
|
|
||||||
when (selection) {
|
|
||||||
is SingleRowSelection.None -> noSelectionSumEquiv
|
|
||||||
is SingleRowSelection.Selected ->
|
|
||||||
when (selection.node) {
|
|
||||||
ViewerNode.Root -> noSelectionSumEquiv
|
|
||||||
is ViewerNode.IssuerNode -> selection.node.sumEquivAmount
|
|
||||||
is ViewerNode.CurrencyNode -> selection.node.equivAmount
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We add some extra timestamp data here to the selected states.
|
|
||||||
*
|
|
||||||
* TODO update this once we have actual timestamps.
|
|
||||||
*/
|
|
||||||
private val stateRows = selectedViewerNodeStates.map { StateRow(LocalDateTime.now(), it) }
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We only display the right pane if a node is selected in the TreeTable.
|
|
||||||
*/
|
|
||||||
private val onlyLeftPaneShown = FXCollections.observableArrayList<Node>(leftPane)
|
|
||||||
private val bothPanesShown = FXCollections.observableArrayList<Node>(leftPane, rightPane)
|
|
||||||
private val panesShown = ChosenList<Node>(selectedViewerNode.map {
|
|
||||||
when (it) {
|
|
||||||
is SingleRowSelection.None -> onlyLeftPaneShown
|
|
||||||
is SingleRowSelection.Selected -> bothPanesShown
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Wire up UI
|
// Wire up UI
|
||||||
init {
|
init {
|
||||||
searchCancelImageView.setOnMouseClicked { event: MouseEvent ->
|
Bindings.bindContent(splitPane.items, view)
|
||||||
if (event.button == MouseButton.PRIMARY) {
|
/**
|
||||||
searchCriteriaTextField.text = ""
|
* We allow filtering by both issuer and currency. We do this by filtering by both at the same time and picking the
|
||||||
|
* one which produces more results, which seems to work, as the set of currency strings don't really overlap with
|
||||||
|
* issuer strings.
|
||||||
|
*/
|
||||||
|
val searchField = SearchField(cashStates, arrayOf(
|
||||||
|
{ state, text -> state.state.data.amount.token.product.toString().contains(text, true) },
|
||||||
|
{ state, text -> state.state.data.amount.token.issuer.party.toString().contains(text, true) }
|
||||||
|
))
|
||||||
|
root.top = searchField.root
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is where we aggregate the list of cash states into the TreeTable structure.
|
||||||
|
*/
|
||||||
|
val cashViewerIssueNodes: ObservableList<TreeItem<out ViewerNode.IssuerNode>> =
|
||||||
|
/**
|
||||||
|
* First we group the states based on the issuer. [memberStates] is all states holding currency issued by [issuer]
|
||||||
|
*/
|
||||||
|
AggregatedList(searchField.filteredData, { it.state.data.amount.token.issuer.party }) { issuer, memberStates ->
|
||||||
|
/**
|
||||||
|
* Next we create subgroups based on currency. [memberStates] here is all states holding currency [currency] issued by [issuer] above.
|
||||||
|
* Note that these states will not be displayed in the TreeTable, but rather in the side pane if the user clicks on the row.
|
||||||
|
*/
|
||||||
|
val currencyNodes = AggregatedList(memberStates, { it.state.data.amount.token.product }) { currency, memberStates ->
|
||||||
|
/**
|
||||||
|
* We sum the states in the subgroup, to be displayed in the "Local Currency" column
|
||||||
|
*/
|
||||||
|
val amounts = memberStates.map { it.state.data.amount.withoutIssuer() }
|
||||||
|
val sumAmount = amounts.foldObservable(Amount(0, currency), Amount<Currency>::plus)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* We exchange the sum to the reporting currency, to be displayed in the "<currency> Equiv" column.
|
||||||
|
*/
|
||||||
|
val equivSumAmount = EasyBind.combine(sumAmount, reportingExchange) { sum, exchange ->
|
||||||
|
exchange.second(sum)
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* Finally assemble the actual TreeTable Currency node.
|
||||||
|
*/
|
||||||
|
TreeItem(ViewerNode.CurrencyNode(sumAmount, equivSumAmount, memberStates))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Now that we have all nodes per currency, we sum the exchanged amounts, to be displayed in the
|
||||||
|
* "<currency> Equiv" column, this time on the issuer level.
|
||||||
|
*/
|
||||||
|
val equivAmounts = currencyNodes.map { it.value.equivAmount }.flatten()
|
||||||
|
val equivSumAmount = reportingCurrency.bind { currency ->
|
||||||
|
equivAmounts.foldObservable(Amount(0, currency), Amount<Currency>::plus)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Assemble the Issuer node.
|
||||||
|
*/
|
||||||
|
val treeItem = TreeItem(ViewerNode.IssuerNode(issuer, equivSumAmount, memberStates))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bind the children in the TreeTable structure.
|
||||||
|
*
|
||||||
|
* TODO Perhaps we shouldn't do this here, but rather have a generic way of binding nodes to the treetable once.
|
||||||
|
*/
|
||||||
|
treeItem.isExpanded = true
|
||||||
|
val children: List<TreeItem<out ViewerNode.IssuerNode>> = treeItem.children
|
||||||
|
Bindings.bindContent(children, currencyNodes)
|
||||||
|
treeItem
|
||||||
|
}
|
||||||
|
|
||||||
|
cashViewerTable.apply() {
|
||||||
|
root = TreeItem()
|
||||||
|
val children: List<TreeItem<out ViewerNode>> = root.children
|
||||||
|
Bindings.bindContent(children, cashViewerIssueNodes)
|
||||||
|
root.isExpanded = true
|
||||||
|
isShowRoot = false
|
||||||
|
// TODO use smart resize
|
||||||
|
setColumnPrefWidthPolicy { tableWidthWithoutPaddingAndBorder, column ->
|
||||||
|
Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / columns.size).toInt()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
val currencyCellFactory = AmountFormatter.boring.toTreeTableCellFactory<ViewerNode, Amount<Currency>>()
|
||||||
Bindings.bindContent(topSplitPane.items, panesShown)
|
|
||||||
|
|
||||||
totalPositionsLabel.textProperty().bind(Bindings.size(selectedViewerNodeStates).map {
|
|
||||||
val plural = if (it == 1) "" else "s"
|
|
||||||
"Total $it position$plural"
|
|
||||||
})
|
|
||||||
|
|
||||||
val equivSumLabelFormatter = AmountFormatter.boring
|
|
||||||
equivSumLabel.textProperty().bind(selectedViewerNodeSumEquiv.map {
|
|
||||||
equivSumLabelFormatter.format(it)
|
|
||||||
})
|
|
||||||
|
|
||||||
Bindings.bindContent(cashStatesList.items, stateRows)
|
|
||||||
|
|
||||||
cashStatesList.setCustomCellFactory { StateRowGraphic(it).root }
|
|
||||||
|
|
||||||
val cellFactory = AmountFormatter.boring.toTreeTableCellFactory<ViewerNode, Amount<Currency>>()
|
|
||||||
|
|
||||||
// TODO use smart resize
|
|
||||||
cashViewerTable.setColumnPrefWidthPolicy { tableWidthWithoutPaddingAndBorder, column ->
|
|
||||||
Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / cashViewerTable.columns.size).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
cashViewerTableIssuerCurrency.setCellValueFactory {
|
cashViewerTableIssuerCurrency.setCellValueFactory {
|
||||||
val node = it.value.value
|
val node = it.value.value
|
||||||
when (node) {
|
when (node) {
|
||||||
ViewerNode.Root -> "".lift()
|
|
||||||
is ViewerNode.IssuerNode -> node.issuer.toString().lift()
|
is ViewerNode.IssuerNode -> node.issuer.toString().lift()
|
||||||
is ViewerNode.CurrencyNode -> node.amount.map { it.token.toString() }
|
is ViewerNode.CurrencyNode -> node.amount.map { it.token.toString() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cashViewerTableLocalCurrency.setCellValueFactory {
|
cashViewerTableLocalCurrency.apply {
|
||||||
val node = it.value.value
|
setCellValueFactory {
|
||||||
when (node) {
|
val node = it.value.value
|
||||||
ViewerNode.Root -> null.lift()
|
when (node) {
|
||||||
is ViewerNode.IssuerNode -> null.lift()
|
is ViewerNode.IssuerNode -> null.lift()
|
||||||
is ViewerNode.CurrencyNode -> node.amount.map { it }
|
is ViewerNode.CurrencyNode -> node.amount.map { it }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
cellFactory = currencyCellFactory
|
||||||
|
/**
|
||||||
|
* We must set this, otherwise on sort an exception will be thrown, as it will try to compare Amounts of differing currency
|
||||||
|
*/
|
||||||
|
isSortable = false
|
||||||
}
|
}
|
||||||
cashViewerTableLocalCurrency.cellFactory = cellFactory
|
|
||||||
/**
|
cashViewerTableEquiv.apply {
|
||||||
* We must set this, otherwise on sort an exception will be thrown, as it will try to compare Amounts of differing currency
|
setCellValueFactory {
|
||||||
*/
|
it.value.value.equivAmount.map { it }
|
||||||
cashViewerTableLocalCurrency.isSortable = false
|
|
||||||
cashViewerTableEquiv.setCellValueFactory {
|
|
||||||
val node = it.value.value
|
|
||||||
when (node) {
|
|
||||||
ViewerNode.Root -> null.lift()
|
|
||||||
is ViewerNode.IssuerNode -> node.sumEquivAmount.map { it }
|
|
||||||
is ViewerNode.CurrencyNode -> node.equivAmount.map { it }
|
|
||||||
}
|
}
|
||||||
|
cellFactory = currencyCellFactory
|
||||||
|
textProperty().bind(reportingCurrency.map { "$it Equiv" })
|
||||||
}
|
}
|
||||||
cashViewerTableEquiv.cellFactory = cellFactory
|
|
||||||
cashViewerTableEquiv.textProperty().bind(reportingCurrency.map { "$it Equiv" })
|
|
||||||
|
|
||||||
cashViewerTable.root = TreeItem(ViewerNode.Root)
|
|
||||||
val children: List<TreeItem<out ViewerNode>> = cashViewerTable.root.children
|
|
||||||
Bindings.bindContent(children, cashViewerIssueNodes)
|
|
||||||
|
|
||||||
cashViewerTable.root.isExpanded = true
|
// Right Pane.
|
||||||
cashViewerTable.isShowRoot = false
|
totalPositionsLabel.textProperty().bind(cashStatesList.itemsProperty().map {
|
||||||
|
val plural = if (it.size == 1) "" else "s"
|
||||||
|
"Total ${it.size} position$plural"
|
||||||
|
})
|
||||||
|
|
||||||
|
cashStatesList.apply {
|
||||||
|
// TODO update this once we have actual timestamps.
|
||||||
|
itemsProperty().bind(selectedNode.map { it?.states?.map { StateRow(LocalDateTime.now(), it) } ?: ObservableListWrapper(emptyList()) })
|
||||||
|
setCustomCellFactory { StateRowGraphic(it).root }
|
||||||
|
}
|
||||||
|
|
||||||
// TODO Think about i18n!
|
// TODO Think about i18n!
|
||||||
totalMatchingLabel.textProperty().bind(Bindings.size(cashViewerIssueNodes).map {
|
totalMatchingLabel.textProperty().bind(Bindings.size(cashViewerIssueNodes).map {
|
||||||
val plural = if (it == 1) "" else "s"
|
val plural = if (it == 1) "" else "s"
|
||||||
"Total $it matching issuer$plural"
|
"Total $it matching issuer$plural"
|
||||||
})
|
})
|
||||||
|
|
||||||
|
toggleButton.setOnAction {
|
||||||
|
cashViewerTable.selectionModel.clearSelection()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,13 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import javafx.scene.Node
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Corda view interface, provides methods to construct various UI component used by the explorer UI framework.
|
||||||
|
* TODO : Implement this interface on all views and register the views with ViewModel when UI start up, then we can use the ViewModel to dynamically create sidebar and dashboard without manual wiring.
|
||||||
|
* TODO : Sidebar icons.
|
||||||
|
*/
|
||||||
|
interface CordaView {
|
||||||
|
val widget: Node?
|
||||||
|
val viewName: String
|
||||||
|
}
|
@ -0,0 +1,42 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import javafx.application.Platform
|
||||||
|
import javafx.util.StringConverter
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to reduce boiler plate code
|
||||||
|
*/
|
||||||
|
fun <T> stringConverter(fromStringFunction: ((String?) -> T)? = null, toStringFunction: (T) -> String): StringConverter<T> {
|
||||||
|
val converter = object : StringConverter<T>() {
|
||||||
|
override fun fromString(string: String?): T {
|
||||||
|
return fromStringFunction?.invoke(string) ?: throw UnsupportedOperationException("not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toString(o: T): String {
|
||||||
|
return toStringFunction(o)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return converter
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Format Number to string with metric prefix.
|
||||||
|
*/
|
||||||
|
fun Number.toStringWithSuffix(precision: Int = 1): String {
|
||||||
|
if (this.toDouble() < 1000) return "$this"
|
||||||
|
val exp = (Math.log(this.toDouble()) / Math.log(1000.0)).toInt()
|
||||||
|
return "${(this.toDouble() / Math.pow(1000.0, exp.toDouble())).format(precision)} ${"kMGTPE"[exp - 1]}"
|
||||||
|
}
|
||||||
|
|
||||||
|
fun Double.format(precision: Int) = String.format("%.${precision}f", this)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method to make sure block runs in FX thread
|
||||||
|
*/
|
||||||
|
fun runInFxApplicationThread(block: () -> Unit) {
|
||||||
|
if (Platform.isFxApplicationThread()) {
|
||||||
|
block()
|
||||||
|
} else {
|
||||||
|
Platform.runLater(block)
|
||||||
|
}
|
||||||
|
}
|
@ -1,65 +1,31 @@
|
|||||||
package com.r3corda.explorer.views
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
|
import com.r3corda.client.model.NetworkIdentityModel
|
||||||
import com.r3corda.client.model.observableValue
|
import com.r3corda.client.model.observableValue
|
||||||
import com.r3corda.explorer.model.SelectedView
|
|
||||||
import com.r3corda.explorer.model.TopLevelModel
|
import com.r3corda.explorer.model.TopLevelModel
|
||||||
import javafx.beans.value.ObservableValue
|
|
||||||
import javafx.scene.control.Button
|
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.image.Image
|
import javafx.scene.control.SplitMenuButton
|
||||||
import javafx.scene.image.ImageView
|
import javafx.scene.image.ImageView
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.layout.GridPane
|
||||||
import org.fxmisc.easybind.EasyBind
|
|
||||||
import tornadofx.View
|
import tornadofx.View
|
||||||
|
|
||||||
class Header : View() {
|
class Header : View() {
|
||||||
override val root: VBox by fxml()
|
override val root: GridPane by fxml()
|
||||||
|
|
||||||
private val sectionIcon: ImageView by fxid()
|
|
||||||
private val sectionIconContainer: VBox by fxid()
|
|
||||||
private val sectionLabel: Label by fxid()
|
private val sectionLabel: Label by fxid()
|
||||||
private val debugNextButton: Button by fxid()
|
private val userButton: SplitMenuButton by fxid()
|
||||||
private val debugGoStopButton: Button by fxid()
|
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
|
||||||
|
private val selectedView by observableValue(TopLevelModel::selectedView)
|
||||||
private val selectedView: ObservableValue<SelectedView> by observableValue(TopLevelModel::selectedView)
|
|
||||||
|
|
||||||
private val homeImage = Image("/com/r3corda/explorer/images/home.png")
|
|
||||||
private val cashImage = Image("/com/r3corda/explorer/images/cash.png")
|
|
||||||
private val transactionImage = Image("/com/r3corda/explorer/images/tx.png")
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
sectionLabel.textProperty().bind(EasyBind.map(selectedView) {
|
sectionLabel.textProperty().bind(selectedView.map { it.displayableName })
|
||||||
when (it) {
|
sectionLabel.graphicProperty().bind(selectedView.map {
|
||||||
SelectedView.Home -> "Home"
|
ImageView(it.image).apply {
|
||||||
SelectedView.Cash -> "Cash"
|
fitHeight = 30.0
|
||||||
SelectedView.Transaction -> "Transactions"
|
fitWidth = 30.0
|
||||||
SelectedView.NewTransaction -> "New Transaction"
|
|
||||||
null -> "Home"
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
userButton.textProperty().bind(myIdentity.map { it?.legalIdentity?.name })
|
||||||
sectionIcon.imageProperty().bind(EasyBind.map(selectedView) {
|
|
||||||
when (it) {
|
|
||||||
SelectedView.Home -> homeImage
|
|
||||||
SelectedView.Cash -> cashImage
|
|
||||||
SelectedView.Transaction -> transactionImage
|
|
||||||
SelectedView.NewTransaction -> cashImage
|
|
||||||
null -> homeImage
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// JavaFX bugs and doesn't invalidate the wrapping Box's height if the icon fit height is first set to
|
|
||||||
// unbounded (0.0) - which is what the label's height is initially, so we set it to 1.0 instead
|
|
||||||
val secionLabelHeightNonZero = EasyBind.map(sectionLabel.heightProperty()) {
|
|
||||||
if (it == 0.0) {
|
|
||||||
1.0
|
|
||||||
} else {
|
|
||||||
it.toDouble()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sectionIconContainer.minWidthProperty().bind(secionLabelHeightNonZero)
|
|
||||||
sectionIcon.fitWidthProperty().bind(secionLabelHeightNonZero)
|
|
||||||
sectionIcon.fitHeightProperty().bind(sectionIcon.fitWidthProperty())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,73 +1,55 @@
|
|||||||
package com.r3corda.explorer.views
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
import com.r3corda.client.fxutils.AmountBindings
|
|
||||||
import com.r3corda.client.fxutils.map
|
import com.r3corda.client.fxutils.map
|
||||||
import com.r3corda.client.model.*
|
import com.r3corda.client.model.GatheredTransactionData
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.client.model.GatheredTransactionDataModel
|
||||||
import com.r3corda.core.contracts.StateAndRef
|
import com.r3corda.client.model.observableListReadOnly
|
||||||
import com.r3corda.core.contracts.withoutIssuer
|
import com.r3corda.client.model.writableValue
|
||||||
import com.r3corda.explorer.formatters.AmountFormatter
|
|
||||||
import com.r3corda.explorer.model.SelectedView
|
import com.r3corda.explorer.model.SelectedView
|
||||||
import com.r3corda.explorer.model.SettingsModel
|
|
||||||
import com.r3corda.explorer.model.TopLevelModel
|
import com.r3corda.explorer.model.TopLevelModel
|
||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
import javafx.beans.value.ObservableValue
|
|
||||||
import javafx.beans.value.WritableValue
|
import javafx.beans.value.WritableValue
|
||||||
import javafx.collections.ObservableList
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.scene.Node
|
||||||
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.Label
|
import javafx.scene.control.Label
|
||||||
import javafx.scene.control.TitledPane
|
import javafx.scene.control.TitledPane
|
||||||
import javafx.scene.input.MouseButton
|
import javafx.scene.input.MouseButton
|
||||||
|
import javafx.scene.input.MouseEvent
|
||||||
import javafx.scene.layout.TilePane
|
import javafx.scene.layout.TilePane
|
||||||
import tornadofx.View
|
import tornadofx.View
|
||||||
import java.util.*
|
import tornadofx.find
|
||||||
|
|
||||||
class Home : View() {
|
class Home : View() {
|
||||||
override val root: TilePane by fxml()
|
override val root: Parent by fxml()
|
||||||
|
private val tilePane: TilePane by fxid()
|
||||||
private val ourCashPane: TitledPane by fxid()
|
private val ourCashPane: TitledPane by fxid()
|
||||||
private val ourCashLabel: Label by fxid()
|
|
||||||
|
|
||||||
private val ourTransactionsPane: TitledPane by fxid()
|
|
||||||
private val ourTransactionsLabel: Label by fxid()
|
private val ourTransactionsLabel: Label by fxid()
|
||||||
|
|
||||||
private val newTransaction: TitledPane by fxid()
|
|
||||||
|
|
||||||
private val selectedView: WritableValue<SelectedView> by writableValue(TopLevelModel::selectedView)
|
private val selectedView: WritableValue<SelectedView> by writableValue(TopLevelModel::selectedView)
|
||||||
private val cashStates: ObservableList<StateAndRef<Cash.State>> by observableList(ContractStateModel::cashStates)
|
|
||||||
private val gatheredTransactionDataList: ObservableList<out GatheredTransactionData>
|
private val gatheredTransactionDataList: ObservableList<out GatheredTransactionData>
|
||||||
by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
||||||
private val reportingCurrency: ObservableValue<Currency> by observableValue(SettingsModel::reportingCurrency)
|
|
||||||
private val exchangeRate: ObservableValue<ExchangeRate> by observableValue(ExchangeRateModel::exchangeRate)
|
|
||||||
|
|
||||||
private val sumAmount = AmountBindings.sumAmountExchange(
|
|
||||||
cashStates.map { it.state.data.amount.withoutIssuer() },
|
|
||||||
reportingCurrency,
|
|
||||||
exchangeRate
|
|
||||||
)
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
val formatter = AmountFormatter.boring
|
// TODO: register views in view model and populate the dashboard dynamically.
|
||||||
|
|
||||||
ourCashLabel.textProperty().bind(sumAmount.map { formatter.format(it) })
|
|
||||||
ourCashPane.setOnMouseClicked { clickEvent ->
|
|
||||||
if (clickEvent.button == MouseButton.PRIMARY) {
|
|
||||||
selectedView.value = SelectedView.Cash
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ourTransactionsLabel.textProperty().bind(
|
ourTransactionsLabel.textProperty().bind(
|
||||||
Bindings.size(gatheredTransactionDataList).map { it.toString() }
|
Bindings.size(gatheredTransactionDataList).map { it.toString() }
|
||||||
)
|
)
|
||||||
ourTransactionsPane.setOnMouseClicked { clickEvent ->
|
|
||||||
if (clickEvent.button == MouseButton.PRIMARY) {
|
ourCashPane.apply {
|
||||||
selectedView.value = SelectedView.Transaction
|
content = find(CashViewer::class).widget
|
||||||
}
|
|
||||||
}
|
|
||||||
newTransaction.setOnMouseClicked { clickEvent ->
|
|
||||||
if (clickEvent.button == MouseButton.PRIMARY) {
|
|
||||||
selectedView.value = SelectedView.NewTransaction
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tilePane.widthProperty().addListener { e ->
|
||||||
|
val prefWidth = 350
|
||||||
|
val columns: Int = ((tilePane.width - 10) / prefWidth).toInt()
|
||||||
|
tilePane.children.forEach { (it as? TitledPane)?.prefWidth = (tilePane.width - 10) / columns }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun changeView(event: MouseEvent) {
|
||||||
|
if (event.button == MouseButton.PRIMARY) {
|
||||||
|
selectedView.value = SelectedView.valueOf((event.source as Node).id)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.google.common.net.HostAndPort
|
||||||
|
import javafx.beans.property.SimpleIntegerProperty
|
||||||
|
import javafx.scene.control.*
|
||||||
|
import javafx.util.converter.IntegerStringConverter
|
||||||
|
import org.controlsfx.dialog.ExceptionDialog
|
||||||
|
import tornadofx.View
|
||||||
|
import java.util.regex.Pattern
|
||||||
|
import kotlin.system.exitProcess
|
||||||
|
|
||||||
|
class LoginView : View() {
|
||||||
|
override val root: DialogPane by fxml()
|
||||||
|
|
||||||
|
private val host by fxid<TextField>()
|
||||||
|
private val port by fxid<TextField>()
|
||||||
|
private val username by fxid<TextField>()
|
||||||
|
private val password by fxid<PasswordField>()
|
||||||
|
private val portProperty = SimpleIntegerProperty()
|
||||||
|
|
||||||
|
fun login(loginFunction: (HostAndPort, String, String) -> Unit) {
|
||||||
|
val loggedIn = Dialog<Boolean>().apply {
|
||||||
|
dialogPane = root
|
||||||
|
var exception = false
|
||||||
|
setResultConverter {
|
||||||
|
exception = false
|
||||||
|
when (it?.buttonData) {
|
||||||
|
ButtonBar.ButtonData.OK_DONE -> try {
|
||||||
|
// TODO : Run this async to avoid UI lockup.
|
||||||
|
loginFunction(HostAndPort.fromParts(host.text, portProperty.value), username.text, password.text)
|
||||||
|
true
|
||||||
|
} catch (e: Exception) {
|
||||||
|
ExceptionDialog(e).showAndWait()
|
||||||
|
exception = true
|
||||||
|
false
|
||||||
|
}
|
||||||
|
else -> false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
setOnCloseRequest {
|
||||||
|
if (!result && !exception) {
|
||||||
|
when (Alert(Alert.AlertType.CONFIRMATION, "Are you sure you want to exit Corda explorer?").apply {
|
||||||
|
}.showAndWait().get()) {
|
||||||
|
ButtonType.OK -> exitProcess(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.showAndWait().get()
|
||||||
|
|
||||||
|
if (!loggedIn) login(loginFunction)
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
// Restrict text field to Integer only.
|
||||||
|
val integerFormat = Pattern.compile("-?(\\d*)").run {
|
||||||
|
TextFormatter<Int>(IntegerStringConverter(), null) { change ->
|
||||||
|
val newText = change.controlNewText
|
||||||
|
if (matcher(newText).matches()) change else null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
port.textFormatter = integerFormat
|
||||||
|
portProperty.bind(integerFormat.valueProperty())
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,9 @@ import com.r3corda.client.model.NodeMonitorModel
|
|||||||
import com.r3corda.client.model.observableList
|
import com.r3corda.client.model.observableList
|
||||||
import com.r3corda.client.model.observableValue
|
import com.r3corda.client.model.observableValue
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.serialization.OpaqueBytes
|
import com.r3corda.core.serialization.OpaqueBytes
|
||||||
import com.r3corda.explorer.components.ExceptionDialog
|
|
||||||
import com.r3corda.explorer.model.CashTransaction
|
import com.r3corda.explorer.model.CashTransaction
|
||||||
import com.r3corda.explorer.model.IdentityModel
|
|
||||||
import com.r3corda.node.services.messaging.CordaRPCOps
|
import com.r3corda.node.services.messaging.CordaRPCOps
|
||||||
import com.r3corda.node.services.messaging.TransactionBuildResult
|
import com.r3corda.node.services.messaging.TransactionBuildResult
|
||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
@ -22,8 +19,8 @@ import javafx.collections.ObservableList
|
|||||||
import javafx.scene.Node
|
import javafx.scene.Node
|
||||||
import javafx.scene.Parent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.*
|
import javafx.scene.control.*
|
||||||
import javafx.util.StringConverter
|
|
||||||
import javafx.util.converter.BigDecimalStringConverter
|
import javafx.util.converter.BigDecimalStringConverter
|
||||||
|
import org.controlsfx.dialog.ExceptionDialog
|
||||||
import tornadofx.View
|
import tornadofx.View
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -43,21 +40,20 @@ class NewTransaction : View() {
|
|||||||
private val transactionTypeCB: ChoiceBox<CashTransaction> by fxid()
|
private val transactionTypeCB: ChoiceBox<CashTransaction> by fxid()
|
||||||
private val amount: TextField by fxid()
|
private val amount: TextField by fxid()
|
||||||
private val currency: ChoiceBox<Currency> by fxid()
|
private val currency: ChoiceBox<Currency> by fxid()
|
||||||
|
|
||||||
private val networkIdentities: ObservableList<NodeInfo> by observableList(NetworkIdentityModel::networkIdentities)
|
|
||||||
|
|
||||||
private val rpcProxy: ObservableValue<CordaRPCOps?> by observableValue(NodeMonitorModel::proxyObservable)
|
|
||||||
private val myIdentity: ObservableValue<Party?> by observableValue(IdentityModel::myIdentity)
|
|
||||||
private val notary: ObservableValue<Party?> by observableValue(IdentityModel::notary)
|
|
||||||
|
|
||||||
private val issueRefLabel: Label by fxid()
|
private val issueRefLabel: Label by fxid()
|
||||||
private val issueRefTextField: TextField by fxid()
|
private val issueRefTextField: TextField by fxid()
|
||||||
|
|
||||||
|
// Inject data
|
||||||
|
private val parties: ObservableList<NodeInfo> by observableList(NetworkIdentityModel::parties)
|
||||||
|
private val rpcProxy: ObservableValue<CordaRPCOps?> by observableValue(NodeMonitorModel::proxyObservable)
|
||||||
|
private val myIdentity: ObservableValue<NodeInfo?> by observableValue(NetworkIdentityModel::myIdentity)
|
||||||
|
private val notaries: ObservableList<NodeInfo> by observableList(NetworkIdentityModel::notaries)
|
||||||
|
|
||||||
private fun ObservableValue<*>.isNotNull(): BooleanBinding {
|
private fun ObservableValue<*>.isNotNull(): BooleanBinding {
|
||||||
return Bindings.createBooleanBinding({ this.value != null }, arrayOf(this))
|
return Bindings.createBooleanBinding({ this.value != null }, arrayOf(this))
|
||||||
}
|
}
|
||||||
|
|
||||||
fun resetScreen() {
|
private fun resetScreen() {
|
||||||
partyBChoiceBox.valueProperty().set(null)
|
partyBChoiceBox.valueProperty().set(null)
|
||||||
transactionTypeCB.valueProperty().set(null)
|
transactionTypeCB.valueProperty().set(null)
|
||||||
currency.valueProperty().set(null)
|
currency.valueProperty().set(null)
|
||||||
@ -66,28 +62,22 @@ class NewTransaction : View() {
|
|||||||
|
|
||||||
init {
|
init {
|
||||||
// Disable everything when not connected to node.
|
// Disable everything when not connected to node.
|
||||||
val enableProperty = myIdentity.isNotNull().and(notary.isNotNull()).and(rpcProxy.isNotNull())
|
val notariesNotNullBinding = Bindings.createBooleanBinding({ notaries.isNotEmpty() }, arrayOf(notaries))
|
||||||
|
val enableProperty = myIdentity.isNotNull().and(rpcProxy.isNotNull()).and(notariesNotNullBinding)
|
||||||
root.disableProperty().bind(enableProperty.not())
|
root.disableProperty().bind(enableProperty.not())
|
||||||
transactionTypeCB.items = FXCollections.observableArrayList(CashTransaction.values().asList())
|
transactionTypeCB.items = FXCollections.observableArrayList(CashTransaction.values().asList())
|
||||||
|
|
||||||
// Party A textfield always display my identity name, not editable.
|
// Party A textfield always display my identity name, not editable.
|
||||||
partyATextField.isEditable = false
|
partyATextField.isEditable = false
|
||||||
partyATextField.textProperty().bind(myIdentity.map { it?.name ?: "" })
|
partyATextField.textProperty().bind(myIdentity.map { it?.legalIdentity?.name ?: "" })
|
||||||
partyALabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA?.let { "$it : " } })
|
partyALabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA?.let { "$it : " } })
|
||||||
partyATextField.visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA }.isNotNull())
|
partyATextField.visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameA }.isNotNull())
|
||||||
|
|
||||||
partyBLabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB?.let { "$it : " } })
|
partyBLabel.textProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB?.let { "$it : " } })
|
||||||
partyBChoiceBox.visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB }.isNotNull())
|
partyBChoiceBox.apply {
|
||||||
partyBChoiceBox.items = networkIdentities
|
visibleProperty().bind(transactionTypeCB.valueProperty().map { it?.partyNameB }.isNotNull())
|
||||||
|
partyBChoiceBox.items = parties
|
||||||
partyBChoiceBox.converter = object : StringConverter<NodeInfo?>() {
|
converter = stringConverter { it?.legalIdentity?.name ?: "" }
|
||||||
override fun toString(node: NodeInfo?): String {
|
|
||||||
return node?.legalIdentity?.name ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun fromString(string: String?): NodeInfo {
|
|
||||||
throw UnsupportedOperationException("not implemented")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BigDecimal text Formatter, restricting text box input to decimal values.
|
// BigDecimal text Formatter, restricting text box input to decimal values.
|
||||||
@ -123,7 +113,8 @@ class NewTransaction : View() {
|
|||||||
executeButton.setOnAction { event ->
|
executeButton.setOnAction { event ->
|
||||||
// Null checks to ensure these observable values are set, execute button should be disabled if any of these value are null, this extra checks are for precaution and getting non-nullable values without using !!.
|
// Null checks to ensure these observable values are set, execute button should be disabled if any of these value are null, this extra checks are for precaution and getting non-nullable values without using !!.
|
||||||
myIdentity.value?.let { myIdentity ->
|
myIdentity.value?.let { myIdentity ->
|
||||||
notary.value?.let { notary ->
|
// TODO : Allow user to chose which notary to use?
|
||||||
|
notaries.first()?.let { notary ->
|
||||||
rpcProxy.value?.let { rpcProxy ->
|
rpcProxy.value?.let { rpcProxy ->
|
||||||
Triple(myIdentity, notary, rpcProxy)
|
Triple(myIdentity, notary, rpcProxy)
|
||||||
}
|
}
|
||||||
@ -131,13 +122,14 @@ class NewTransaction : View() {
|
|||||||
}?.let {
|
}?.let {
|
||||||
val (myIdentity, notary, rpcProxy) = it
|
val (myIdentity, notary, rpcProxy) = it
|
||||||
transactionTypeCB.value?.let {
|
transactionTypeCB.value?.let {
|
||||||
|
// Default issuer reference to 1 if not specified.
|
||||||
val issueRef = OpaqueBytes(if (issueRefTextField.text.trim().isNotBlank()) issueRefTextField.text.toByteArray() else ByteArray(1, { 1 }))
|
val issueRef = OpaqueBytes(if (issueRefTextField.text.trim().isNotBlank()) issueRefTextField.text.toByteArray() else ByteArray(1, { 1 }))
|
||||||
|
// TODO : Change these commands into individual RPC methods instead of using executeCommand.
|
||||||
val command = when (it) {
|
val command = when (it) {
|
||||||
CashTransaction.Issue -> ClientToServiceCommand.IssueCash(Amount(textFormatter.value, currency.value), issueRef, partyBChoiceBox.value.legalIdentity, notary)
|
CashTransaction.Issue -> ClientToServiceCommand.IssueCash(Amount(textFormatter.value, currency.value), issueRef, partyBChoiceBox.value.legalIdentity, notary.notaryIdentity)
|
||||||
CashTransaction.Pay -> ClientToServiceCommand.PayCash(Amount(textFormatter.value, Issued(PartyAndReference(myIdentity, issueRef), currency.value)), partyBChoiceBox.value.legalIdentity)
|
CashTransaction.Pay -> ClientToServiceCommand.PayCash(Amount(textFormatter.value, Issued(PartyAndReference(myIdentity.legalIdentity, issueRef), currency.value)), partyBChoiceBox.value.legalIdentity)
|
||||||
CashTransaction.Exit -> ClientToServiceCommand.ExitCash(Amount(textFormatter.value, currency.value), issueRef)
|
CashTransaction.Exit -> ClientToServiceCommand.ExitCash(Amount(textFormatter.value, currency.value), issueRef)
|
||||||
}
|
}
|
||||||
|
|
||||||
val dialog = Alert(Alert.AlertType.INFORMATION).apply {
|
val dialog = Alert(Alert.AlertType.INFORMATION).apply {
|
||||||
headerText = null
|
headerText = null
|
||||||
contentText = "Transaction Started."
|
contentText = "Transaction Started."
|
||||||
@ -165,8 +157,7 @@ class NewTransaction : View() {
|
|||||||
dialog.close()
|
dialog.close()
|
||||||
ExceptionDialog(it.source.exception).apply {
|
ExceptionDialog(it.source.exception).apply {
|
||||||
initOwner((event.target as Node).scene.window)
|
initOwner((event.target as Node).scene.window)
|
||||||
showAndWait()
|
}.showAndWait()
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,37 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.ChosenList
|
||||||
|
import com.r3corda.client.fxutils.filter
|
||||||
|
import com.r3corda.client.fxutils.lift
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
|
import javafx.collections.ObservableList
|
||||||
|
import javafx.scene.Parent
|
||||||
|
import javafx.scene.control.TextField
|
||||||
|
import javafx.scene.image.ImageView
|
||||||
|
import javafx.scene.input.MouseButton
|
||||||
|
import javafx.scene.input.MouseEvent
|
||||||
|
import tornadofx.UIComponent
|
||||||
|
import tornadofx.observable
|
||||||
|
|
||||||
|
class SearchField<T>(private val data: ObservableList<T>, filterCriteria: Array<(T, String) -> Boolean>) : UIComponent() {
|
||||||
|
|
||||||
|
override val root: Parent by fxml()
|
||||||
|
private val textField by fxid<TextField>()
|
||||||
|
private val clearButton by fxid<ImageView>()
|
||||||
|
|
||||||
|
// Currently this method apply each filter to the collection and return the collection with most matches.
|
||||||
|
// TODO : Allow user to chose if there are matches in multiple category.
|
||||||
|
val filteredData = ChosenList(textField.textProperty().map { text ->
|
||||||
|
if (text.isBlank()) data else filterCriteria.map { criterion ->
|
||||||
|
data.filter({ state: T -> criterion(state, text) }.lift())
|
||||||
|
}.maxBy { it.size } ?: emptyList<T>().observable()
|
||||||
|
})
|
||||||
|
|
||||||
|
init {
|
||||||
|
clearButton.setOnMouseClicked { event: MouseEvent ->
|
||||||
|
if (event.button == MouseButton.PRIMARY) {
|
||||||
|
textField.text = ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,51 @@
|
|||||||
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
|
import com.r3corda.client.model.writableValue
|
||||||
|
import com.r3corda.explorer.model.SelectedView
|
||||||
|
import com.r3corda.explorer.model.TopLevelModel
|
||||||
|
import javafx.beans.value.WritableValue
|
||||||
|
import javafx.geometry.Pos
|
||||||
|
import javafx.scene.control.ContentDisplay
|
||||||
|
import javafx.scene.input.MouseButton
|
||||||
|
import javafx.scene.layout.VBox
|
||||||
|
import javafx.scene.text.Font
|
||||||
|
import javafx.scene.text.TextAlignment
|
||||||
|
import tornadofx.View
|
||||||
|
import tornadofx.button
|
||||||
|
import tornadofx.imageview
|
||||||
|
|
||||||
|
class Sidebar : View() {
|
||||||
|
override val root: VBox by fxml()
|
||||||
|
private val selectedView: WritableValue<SelectedView> by writableValue(TopLevelModel::selectedView)
|
||||||
|
|
||||||
|
init {
|
||||||
|
// TODO: Obtain views from ViewModel.
|
||||||
|
arrayOf(SelectedView.Home, SelectedView.Cash, SelectedView.Transaction, SelectedView.NewTransaction, SelectedView.Network, SelectedView.Setting).forEach { view ->
|
||||||
|
root.apply {
|
||||||
|
button(view.displayableName) {
|
||||||
|
graphic = imageview {
|
||||||
|
image = view.image
|
||||||
|
// TODO : Use CSS instead.
|
||||||
|
fitWidth = 35.0
|
||||||
|
fitHeight = 35.0
|
||||||
|
}
|
||||||
|
styleClass.add("sidebar-menu-item")
|
||||||
|
setOnMouseClicked { e ->
|
||||||
|
if (e.button == MouseButton.PRIMARY) {
|
||||||
|
selectedView.value = view
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Transform to smaller icon layout when sidebar width is below 150.
|
||||||
|
val smallIconProperty = widthProperty().map { (it.toDouble() < 150) }
|
||||||
|
|
||||||
|
contentDisplayProperty().bind(smallIconProperty.map { if (it) ContentDisplay.TOP else ContentDisplay.LEFT })
|
||||||
|
textAlignmentProperty().bind(smallIconProperty.map { if (it) TextAlignment.CENTER else TextAlignment.LEFT })
|
||||||
|
alignmentProperty().bind(smallIconProperty.map { if (it) Pos.CENTER else Pos.CENTER_LEFT })
|
||||||
|
fontProperty().bind(smallIconProperty.map { if (it) Font.font(9.0) else Font.font(13.0) })
|
||||||
|
wrapTextProperty().bind(smallIconProperty)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,22 +1,29 @@
|
|||||||
package com.r3corda.explorer.views
|
package com.r3corda.explorer.views
|
||||||
|
|
||||||
|
import com.r3corda.client.fxutils.map
|
||||||
import com.r3corda.client.model.objectProperty
|
import com.r3corda.client.model.objectProperty
|
||||||
import com.r3corda.explorer.model.SelectedView
|
import com.r3corda.explorer.model.SelectedView
|
||||||
import com.r3corda.explorer.model.TopLevelModel
|
import com.r3corda.explorer.model.TopLevelModel
|
||||||
import javafx.beans.property.ObjectProperty
|
import javafx.beans.property.ObjectProperty
|
||||||
import javafx.scene.input.KeyCode
|
import javafx.geometry.Pos
|
||||||
import javafx.scene.input.KeyEvent
|
import javafx.scene.Parent
|
||||||
import javafx.scene.layout.BorderPane
|
import javafx.scene.layout.BorderPane
|
||||||
|
import javafx.scene.layout.GridPane
|
||||||
|
import javafx.scene.layout.Pane
|
||||||
import javafx.scene.layout.Priority
|
import javafx.scene.layout.Priority
|
||||||
import javafx.scene.layout.VBox
|
import javafx.scene.text.TextAlignment
|
||||||
import org.fxmisc.easybind.EasyBind
|
|
||||||
import tornadofx.View
|
import tornadofx.View
|
||||||
|
import tornadofx.add
|
||||||
|
import tornadofx.gridpane
|
||||||
|
import tornadofx.label
|
||||||
|
|
||||||
class TopLevel : View() {
|
class TopLevel : View() {
|
||||||
override val root: VBox by fxml()
|
override val root: Parent by fxml()
|
||||||
val selectionBorderPane: BorderPane by fxid()
|
val selectionBorderPane: BorderPane by fxid()
|
||||||
|
val sidebarPane: Pane by fxid()
|
||||||
|
|
||||||
private val header: Header by inject()
|
private val header: Header by inject()
|
||||||
|
private val sidebar: Sidebar by inject()
|
||||||
private val home: Home by inject()
|
private val home: Home by inject()
|
||||||
private val cash: CashViewer by inject()
|
private val cash: CashViewer by inject()
|
||||||
private val transaction: TransactionViewer by inject()
|
private val transaction: TransactionViewer by inject()
|
||||||
@ -29,24 +36,28 @@ class TopLevel : View() {
|
|||||||
private val transactionRoot = transaction.root
|
private val transactionRoot = transaction.root
|
||||||
private val newTransactionRoot = newTransaction.root
|
private val newTransactionRoot = newTransaction.root
|
||||||
|
|
||||||
private fun getView(selection: SelectedView) = when (selection) {
|
|
||||||
SelectedView.Home -> homeRoot
|
|
||||||
SelectedView.Cash -> cashRoot
|
|
||||||
SelectedView.Transaction -> transactionRoot
|
|
||||||
SelectedView.NewTransaction -> newTransactionRoot
|
|
||||||
}
|
|
||||||
val selectedView: ObjectProperty<SelectedView> by objectProperty(TopLevelModel::selectedView)
|
val selectedView: ObjectProperty<SelectedView> by objectProperty(TopLevelModel::selectedView)
|
||||||
|
|
||||||
init {
|
init {
|
||||||
VBox.setVgrow(selectionBorderPane, Priority.ALWAYS)
|
selectionBorderPane.centerProperty().bind(selectedView.map {
|
||||||
selectionBorderPane.centerProperty().bind(EasyBind.map(selectedView) { getView(it) })
|
when (it) {
|
||||||
|
SelectedView.Home -> homeRoot
|
||||||
primaryStage.addEventHandler(KeyEvent.KEY_RELEASED) { keyEvent ->
|
SelectedView.Cash -> cashRoot
|
||||||
if (keyEvent.code == KeyCode.ESCAPE) {
|
SelectedView.Transaction -> transactionRoot
|
||||||
selectedView.value = SelectedView.Home
|
SelectedView.NewTransaction -> newTransactionRoot
|
||||||
|
else -> gridpane {
|
||||||
|
label("Under Construction...") {
|
||||||
|
maxWidth = Double.MAX_VALUE
|
||||||
|
textAlignment = TextAlignment.CENTER
|
||||||
|
alignment = Pos.CENTER
|
||||||
|
GridPane.setVgrow(this, Priority.ALWAYS)
|
||||||
|
GridPane.setHgrow(this, Priority.ALWAYS)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
|
selectionBorderPane.center.styleClass.add("no-padding")
|
||||||
root.children.add(0, header.root)
|
sidebarPane.add(sidebar.root)
|
||||||
|
selectionBorderPane.top = header.root
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -4,82 +4,43 @@ import com.r3corda.client.fxutils.*
|
|||||||
import com.r3corda.client.model.*
|
import com.r3corda.client.model.*
|
||||||
import com.r3corda.contracts.asset.Cash
|
import com.r3corda.contracts.asset.Cash
|
||||||
import com.r3corda.core.contracts.*
|
import com.r3corda.core.contracts.*
|
||||||
import com.r3corda.core.crypto.Party
|
|
||||||
import com.r3corda.core.crypto.SecureHash
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.toStringShort
|
import com.r3corda.core.crypto.toStringShort
|
||||||
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
import com.r3corda.explorer.AmountDiff
|
import com.r3corda.explorer.AmountDiff
|
||||||
import com.r3corda.explorer.formatters.AmountFormatter
|
import com.r3corda.explorer.formatters.AmountFormatter
|
||||||
import com.r3corda.explorer.formatters.Formatter
|
import com.r3corda.explorer.identicon.identicon
|
||||||
import com.r3corda.explorer.formatters.NumberFormatter
|
import com.r3corda.explorer.identicon.identiconToolTip
|
||||||
import com.r3corda.explorer.model.IdentityModel
|
|
||||||
import com.r3corda.explorer.model.ReportingCurrencyModel
|
import com.r3corda.explorer.model.ReportingCurrencyModel
|
||||||
import com.r3corda.explorer.sign
|
import com.r3corda.explorer.sign
|
||||||
import com.r3corda.explorer.ui.*
|
import com.r3corda.explorer.ui.setCustomCellFactory
|
||||||
import javafx.beans.binding.Bindings
|
import javafx.beans.binding.Bindings
|
||||||
import javafx.beans.value.ObservableValue
|
import javafx.beans.value.ObservableValue
|
||||||
import javafx.collections.FXCollections
|
import javafx.collections.FXCollections
|
||||||
import javafx.collections.ObservableList
|
|
||||||
import javafx.geometry.Insets
|
import javafx.geometry.Insets
|
||||||
import javafx.scene.Node
|
import javafx.scene.Parent
|
||||||
import javafx.scene.control.*
|
import javafx.scene.control.Label
|
||||||
|
import javafx.scene.control.ListView
|
||||||
|
import javafx.scene.control.TableView
|
||||||
|
import javafx.scene.control.TitledPane
|
||||||
import javafx.scene.layout.Background
|
import javafx.scene.layout.Background
|
||||||
import javafx.scene.layout.BackgroundFill
|
import javafx.scene.layout.BackgroundFill
|
||||||
|
import javafx.scene.layout.BorderPane
|
||||||
import javafx.scene.layout.CornerRadii
|
import javafx.scene.layout.CornerRadii
|
||||||
import javafx.scene.layout.VBox
|
|
||||||
import javafx.scene.paint.Color
|
import javafx.scene.paint.Color
|
||||||
import tornadofx.View
|
import tornadofx.*
|
||||||
import java.security.PublicKey
|
import java.security.PublicKey
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
class TransactionViewer: View() {
|
class TransactionViewer : View() {
|
||||||
override val root: VBox by fxml()
|
override val root by fxml<BorderPane>()
|
||||||
|
private val transactionViewTable by fxid<TableView<ViewerNode>>()
|
||||||
val topSplitPane: SplitPane by fxid()
|
private val matchingTransactionsLabel by fxid<Label>()
|
||||||
|
|
||||||
// Top half (transactions table)
|
|
||||||
private val transactionViewTable: TableView<ViewerNode> by fxid()
|
|
||||||
private val transactionViewTransactionId: TableColumn<ViewerNode, String> by fxid()
|
|
||||||
private val transactionViewStateMachineId: TableColumn<ViewerNode, String> by fxid()
|
|
||||||
private val transactionViewClientUuid: TableColumn<ViewerNode, String> by fxid()
|
|
||||||
private val transactionViewTransactionStatus: TableColumn<ViewerNode, TransactionCreateStatus?> by fxid()
|
|
||||||
private val transactionViewProtocolStatus: TableColumn<ViewerNode, String> by fxid()
|
|
||||||
private val transactionViewStateMachineStatus: TableColumn<ViewerNode, StateMachineStatus?> by fxid()
|
|
||||||
private val transactionViewCommandTypes: TableColumn<ViewerNode, String> by fxid()
|
|
||||||
private val transactionViewTotalValueEquiv: TableColumn<ViewerNode, AmountDiff<Currency>> by fxid()
|
|
||||||
|
|
||||||
// Bottom half (details)
|
|
||||||
private val contractStatesTitledPane: TitledPane by fxid()
|
|
||||||
|
|
||||||
private val contractStatesInputsCountLabel: Label by fxid()
|
|
||||||
private val contractStatesInputStatesTable: TableView<StateNode> by fxid()
|
|
||||||
private val contractStatesInputStatesId: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesInputStatesType: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesInputStatesOwner: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesInputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid()
|
|
||||||
private val contractStatesInputStatesAmount: TableColumn<StateNode, Long?> by fxid()
|
|
||||||
private val contractStatesInputStatesEquiv: TableColumn<StateNode, Amount<Currency>?> by fxid()
|
|
||||||
|
|
||||||
private val contractStatesOutputsCountLabel: Label by fxid()
|
|
||||||
private val contractStatesOutputStatesTable: TableView<StateNode> by fxid()
|
|
||||||
private val contractStatesOutputStatesId: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesOutputStatesType: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesOutputStatesOwner: TableColumn<StateNode, String> by fxid()
|
|
||||||
private val contractStatesOutputStatesLocalCurrency: TableColumn<StateNode, Currency?> by fxid()
|
|
||||||
private val contractStatesOutputStatesAmount: TableColumn<StateNode, Long?> by fxid()
|
|
||||||
private val contractStatesOutputStatesEquiv: TableColumn<StateNode, Amount<Currency>?> by fxid()
|
|
||||||
|
|
||||||
private val signaturesTitledPane: TitledPane by fxid()
|
|
||||||
private val signaturesList: ListView<PublicKey> by fxid()
|
|
||||||
|
|
||||||
private val matchingTransactionsLabel: Label by fxid()
|
|
||||||
|
|
||||||
// Inject data
|
// Inject data
|
||||||
private val gatheredTransactionDataList: ObservableList<out GatheredTransactionData>
|
private val gatheredTransactionDataList by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
||||||
by observableListReadOnly(GatheredTransactionDataModel::gatheredTransactionDataList)
|
private val reportingExchange by observableValue(ReportingCurrencyModel::reportingExchange)
|
||||||
private val reportingExchange: ObservableValue<Pair<Currency, (Amount<Currency>) -> Amount<Currency>>>
|
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
|
||||||
by observableValue(ReportingCurrencyModel::reportingExchange)
|
|
||||||
private val myIdentity: ObservableValue<Party?> by observableValue(IdentityModel::myIdentity)
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
* This is what holds data for a single transaction node. Note how a lot of these are nullable as we often simply don't
|
||||||
@ -92,7 +53,7 @@ class TransactionViewer: View() {
|
|||||||
val stateMachineStatus: ObservableValue<out StateMachineStatus?>,
|
val stateMachineStatus: ObservableValue<out StateMachineStatus?>,
|
||||||
val protocolStatus: ObservableValue<out ProtocolStatus?>,
|
val protocolStatus: ObservableValue<out ProtocolStatus?>,
|
||||||
val commandTypes: Collection<Class<CommandData>>,
|
val commandTypes: Collection<Class<CommandData>>,
|
||||||
val totalValueEquiv: ObservableValue<AmountDiff<Currency>?>
|
val totalValueEquiv: ObservableValue<AmountDiff<Currency>>
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -121,282 +82,148 @@ class TransactionViewer: View() {
|
|||||||
stateMachineStatus = stateMachineProperty { it.stateMachineStatus },
|
stateMachineStatus = stateMachineProperty { it.stateMachineStatus },
|
||||||
commandTypes = it.transaction.transaction.tx.commands.map { it.value.javaClass },
|
commandTypes = it.transaction.transaction.tx.commands.map { it.value.javaClass },
|
||||||
totalValueEquiv = {
|
totalValueEquiv = {
|
||||||
val resolvedInputs = it.transaction.inputs.sequence().map { resolution ->
|
val resolvedInputs = it.transaction.inputs.sequence()
|
||||||
when (resolution) {
|
.map { (it as? PartiallyResolvedTransaction.InputResolution.Resolved)?.stateAndRef?.state }
|
||||||
is PartiallyResolvedTransaction.InputResolution.Unresolved -> null
|
.filterNotNull().toList().lift()
|
||||||
is PartiallyResolvedTransaction.InputResolution.Resolved -> resolution.stateAndRef
|
|
||||||
}
|
|
||||||
}.fold(listOf()) { inputs: List<StateAndRef<ContractState>>?, state: StateAndRef<ContractState>? ->
|
|
||||||
if (inputs != null && state != null) {
|
|
||||||
inputs + state
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
::calculateTotalEquiv.lift(
|
::calculateTotalEquiv.lift(
|
||||||
myIdentity,
|
myIdentity,
|
||||||
reportingExchange,
|
reportingExchange,
|
||||||
resolvedInputs.lift(),
|
resolvedInputs,
|
||||||
it.transaction.transaction.tx.outputs.lift()
|
it.transaction.transaction.tx.outputs.lift()
|
||||||
)
|
)
|
||||||
}()
|
}()
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The detail panes are only filled out if a transaction is selected
|
|
||||||
*/
|
|
||||||
private val selectedViewerNode = transactionViewTable.singleRowSelection()
|
|
||||||
private val selectedTransaction = selectedViewerNode.map {
|
|
||||||
when (it) {
|
|
||||||
is SingleRowSelection.None -> null
|
|
||||||
is SingleRowSelection.Selected -> it.node.transaction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val inputStateNodes = ChosenList(selectedTransaction.map { transaction ->
|
|
||||||
if (transaction == null) {
|
|
||||||
FXCollections.emptyObservableList<StateNode>()
|
|
||||||
} else {
|
|
||||||
FXCollections.observableArrayList(transaction.inputs.map { inputResolution ->
|
|
||||||
StateNode(inputResolution, inputResolution.value.stateRef)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private val outputStateNodes = ChosenList(selectedTransaction.map {
|
|
||||||
if (it == null) {
|
|
||||||
FXCollections.emptyObservableList<StateNode>()
|
|
||||||
} else {
|
|
||||||
FXCollections.observableArrayList(it.transaction.tx.outputs.mapIndexed { index, transactionState ->
|
|
||||||
val stateRef = StateRef(it.id, index)
|
|
||||||
StateNode(PartiallyResolvedTransaction.InputResolution.Resolved(StateAndRef(transactionState, stateRef)).lift(), stateRef)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
private val signatures = ChosenList(selectedTransaction.map {
|
|
||||||
if (it == null) {
|
|
||||||
FXCollections.emptyObservableList<PublicKey>()
|
|
||||||
} else {
|
|
||||||
FXCollections.observableArrayList(it.transaction.sigs.map { it.by })
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* We only display the detail panes if there is a node selected.
|
|
||||||
*/
|
|
||||||
private val allNodesShown = FXCollections.observableArrayList<Node>(
|
|
||||||
transactionViewTable,
|
|
||||||
contractStatesTitledPane,
|
|
||||||
signaturesTitledPane
|
|
||||||
)
|
|
||||||
private val onlyTransactionsTableShown = FXCollections.observableArrayList<Node>(
|
|
||||||
transactionViewTable
|
|
||||||
)
|
|
||||||
private val topSplitPaneNodesShown = ChosenList(
|
|
||||||
selectedViewerNode.map { selection ->
|
|
||||||
if (selection is SingleRowSelection.None<*>) {
|
|
||||||
onlyTransactionsTableShown
|
|
||||||
} else {
|
|
||||||
allNodesShown
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Both input and output state tables look the same, so we each up with [wireUpStatesTable]
|
|
||||||
*/
|
|
||||||
private fun wireUpStatesTable(
|
|
||||||
states: ObservableList<StateNode>,
|
|
||||||
statesCountLabel: Label,
|
|
||||||
statesTable: TableView<StateNode>,
|
|
||||||
statesId: TableColumn<StateNode, String>,
|
|
||||||
statesType: TableColumn<StateNode, String>,
|
|
||||||
statesOwner: TableColumn<StateNode, String>,
|
|
||||||
statesLocalCurrency: TableColumn<StateNode, Currency?>,
|
|
||||||
statesAmount: TableColumn<StateNode, Long?>,
|
|
||||||
statesEquiv: TableColumn<StateNode, Amount<Currency>?>
|
|
||||||
) {
|
|
||||||
statesCountLabel.textProperty().bind(Bindings.size(states).map { "$it" })
|
|
||||||
|
|
||||||
Bindings.bindContent(statesTable.items, states)
|
|
||||||
|
|
||||||
val unknownString = "???"
|
|
||||||
|
|
||||||
statesId.setCellValueFactory { it.value.stateRef.toString().lift() }
|
|
||||||
statesType.setCellValueFactory {
|
|
||||||
resolvedOrDefault(it.value.state, unknownString) {
|
|
||||||
it.state.data.javaClass.toString()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statesOwner.setCellValueFactory {
|
|
||||||
resolvedOrDefault(it.value.state, unknownString) {
|
|
||||||
val contractState = it.state.data
|
|
||||||
if (contractState is OwnableState) {
|
|
||||||
contractState.owner.toStringShort()
|
|
||||||
} else {
|
|
||||||
unknownString
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statesLocalCurrency.setCellValueFactory {
|
|
||||||
resolvedOrDefault<Currency?>(it.value.state, null) {
|
|
||||||
val contractState = it.state.data
|
|
||||||
if (contractState is Cash.State) {
|
|
||||||
contractState.amount.token.product
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statesAmount.setCellValueFactory {
|
|
||||||
resolvedOrDefault<Long?>(it.value.state, null) {
|
|
||||||
val contractState = it.state.data
|
|
||||||
if (contractState is Cash.State) {
|
|
||||||
contractState.amount.quantity
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
statesAmount.cellFactory = NumberFormatter.boringLong.toTableCellFactory()
|
|
||||||
statesEquiv.setCellValueFactory {
|
|
||||||
resolvedOrDefault<ObservableValue<Amount<Currency>?>>(it.value.state, null.lift()) {
|
|
||||||
val contractState = it.state.data
|
|
||||||
if (contractState is Cash.State) {
|
|
||||||
reportingExchange.map { exchange ->
|
|
||||||
exchange.second(contractState.amount.withoutIssuer())
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
null.lift()
|
|
||||||
}
|
|
||||||
}.bind { it }
|
|
||||||
|
|
||||||
}
|
|
||||||
statesEquiv.cellFactory = AmountFormatter.boring.toTableCellFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
init {
|
||||||
Bindings.bindContent(topSplitPane.items, topSplitPaneNodesShown)
|
val searchField = SearchField(viewerNodes, arrayOf({ viewerNode, s -> viewerNode.commandTypes.any { it.simpleName.contains(s, true) } }))
|
||||||
|
root.top = searchField.root
|
||||||
// Transaction table
|
// Transaction table
|
||||||
Bindings.bindContent(transactionViewTable.items, viewerNodes)
|
transactionViewTable.apply {
|
||||||
|
items = searchField.filteredData
|
||||||
transactionViewTable.setColumnPrefWidthPolicy { tableWidthWithoutPaddingAndBorder, column ->
|
column("Transaction ID", ViewerNode::transactionId).setCustomCellFactory {
|
||||||
Math.floor(tableWidthWithoutPaddingAndBorder.toDouble() / transactionViewTable.columns.size).toInt()
|
label("$it".substring(0, 16) + "...") {
|
||||||
}
|
graphic = imageview {
|
||||||
|
image = identicon(it, 5.0)
|
||||||
transactionViewTransactionId.setCellValueFactory { "${it.value.transactionId}".lift() }
|
}
|
||||||
transactionViewStateMachineId.setCellValueFactory { it.value.stateMachineRunId.map { "${it?.uuid ?: ""}" } }
|
tooltip = identiconToolTip(it)
|
||||||
transactionViewProtocolStatus.setCellValueFactory { it.value.protocolStatus.map { "${it ?: ""}" } }
|
}
|
||||||
transactionViewTransactionStatus.setCustomCellFactory {
|
|
||||||
val label = Label()
|
|
||||||
val backgroundFill = when (it) {
|
|
||||||
is TransactionCreateStatus.Started -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)
|
|
||||||
is TransactionCreateStatus.Failed -> BackgroundFill(Color.SALMON, CornerRadii.EMPTY, Insets.EMPTY)
|
|
||||||
null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)
|
|
||||||
}
|
}
|
||||||
label.background = Background(backgroundFill)
|
column("State Machine ID", ViewerNode::stateMachineRunId).cellFormat { text = "${it?.uuid ?: ""}" }
|
||||||
label.text = "$it"
|
column("Protocol status", ViewerNode::protocolStatus).cellFormat { text = "${it.value ?: ""}" }
|
||||||
label
|
column("SM Status", ViewerNode::stateMachineStatus).cellFormat { text = "${it.value ?: ""}" }
|
||||||
}
|
column("Command type(s)", ViewerNode::commandTypes).cellFormat { text = it.map { it.simpleName }.joinToString(",") }
|
||||||
transactionViewStateMachineStatus.setCellValueFactory { it.value.stateMachineStatus.map { it } }
|
column("Total value (USD equiv)", ViewerNode::totalValueEquiv)
|
||||||
transactionViewStateMachineStatus.setCustomCellFactory {
|
.cellFormat { text = "${it.positivity.sign}${AmountFormatter.boring.format(it.amount)}" }
|
||||||
val label = Label()
|
rowExpander(true) {
|
||||||
val backgroundFill = when (it) {
|
add(ContractStatesView(it.transaction).root)
|
||||||
is StateMachineStatus.Added -> BackgroundFill(Color.LIGHTYELLOW, CornerRadii.EMPTY, Insets.EMPTY)
|
background = Background(BackgroundFill(Color.WHITE, CornerRadii.EMPTY, Insets.EMPTY))
|
||||||
is StateMachineStatus.Removed -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)
|
prefHeight = 400.0
|
||||||
null -> BackgroundFill(Color.TRANSPARENT, CornerRadii.EMPTY, Insets.EMPTY)
|
}.apply {
|
||||||
|
// Hide the expander column.
|
||||||
|
isVisible = false
|
||||||
|
prefWidth = 0.0
|
||||||
}
|
}
|
||||||
label.background = Background(backgroundFill)
|
|
||||||
label.text = "$it"
|
|
||||||
label
|
|
||||||
}
|
}
|
||||||
|
|
||||||
transactionViewCommandTypes.setCellValueFactory {
|
matchingTransactionsLabel.textProperty().bind(Bindings.size(transactionViewTable.items).map {
|
||||||
it.value.commandTypes.map { it.simpleName }.joinToString(",").lift()
|
|
||||||
}
|
|
||||||
transactionViewTotalValueEquiv.setCellValueFactory<ViewerNode, AmountDiff<Currency>> { it.value.totalValueEquiv }
|
|
||||||
transactionViewTotalValueEquiv.cellFactory = object : Formatter<AmountDiff<Currency>> {
|
|
||||||
override fun format(value: AmountDiff<Currency>) =
|
|
||||||
"${value.positivity.sign}${AmountFormatter.boring.format(value.amount)}"
|
|
||||||
}.toTableCellFactory()
|
|
||||||
|
|
||||||
// Contract states
|
|
||||||
wireUpStatesTable(
|
|
||||||
inputStateNodes,
|
|
||||||
contractStatesInputsCountLabel,
|
|
||||||
contractStatesInputStatesTable,
|
|
||||||
contractStatesInputStatesId,
|
|
||||||
contractStatesInputStatesType,
|
|
||||||
contractStatesInputStatesOwner,
|
|
||||||
contractStatesInputStatesLocalCurrency,
|
|
||||||
contractStatesInputStatesAmount,
|
|
||||||
contractStatesInputStatesEquiv
|
|
||||||
)
|
|
||||||
wireUpStatesTable(
|
|
||||||
outputStateNodes,
|
|
||||||
contractStatesOutputsCountLabel,
|
|
||||||
contractStatesOutputStatesTable,
|
|
||||||
contractStatesOutputStatesId,
|
|
||||||
contractStatesOutputStatesType,
|
|
||||||
contractStatesOutputStatesOwner,
|
|
||||||
contractStatesOutputStatesLocalCurrency,
|
|
||||||
contractStatesOutputStatesAmount,
|
|
||||||
contractStatesOutputStatesEquiv
|
|
||||||
)
|
|
||||||
|
|
||||||
// Signatures
|
|
||||||
Bindings.bindContent(signaturesList.items, signatures)
|
|
||||||
signaturesList.cellFactory = object : Formatter<PublicKey> {
|
|
||||||
override fun format(value: PublicKey) = value.toStringShort()
|
|
||||||
}.toListCellFactory()
|
|
||||||
|
|
||||||
matchingTransactionsLabel.textProperty().bind(Bindings.size(viewerNodes).map {
|
|
||||||
"$it matching transaction${if (it == 1) "" else "s"}"
|
"$it matching transaction${if (it == 1) "" else "s"}"
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class ContractStatesView(val transaction: PartiallyResolvedTransaction) : View() {
|
||||||
|
override val root: Parent by fxml()
|
||||||
|
private val inputs: ListView<StateNode> by fxid()
|
||||||
|
private val outputs: ListView<StateNode> by fxid()
|
||||||
|
private val signatures: ListView<PublicKey> by fxid()
|
||||||
|
private val inputPane: TitledPane by fxid()
|
||||||
|
private val outputPane: TitledPane by fxid()
|
||||||
|
private val signaturesPane: TitledPane by fxid()
|
||||||
|
|
||||||
|
init {
|
||||||
|
val inputStates = transaction.inputs.map { StateNode(it, it.value.stateRef) }
|
||||||
|
val outputStates = transaction.transaction.tx.outputs.mapIndexed { index, transactionState ->
|
||||||
|
val stateRef = StateRef(transaction.id, index)
|
||||||
|
StateNode(PartiallyResolvedTransaction.InputResolution.Resolved(StateAndRef(transactionState, stateRef)).lift(), stateRef)
|
||||||
|
}
|
||||||
|
|
||||||
|
val signatureData = transaction.transaction.sigs.map { it.by }
|
||||||
|
// Bind count to TitlePane
|
||||||
|
inputPane.textProperty().bind(inputStates.lift().map { "Input (${it.count()})" })
|
||||||
|
outputPane.textProperty().bind(outputStates.lift().map { "Output (${it.count()})" })
|
||||||
|
signaturesPane.textProperty().bind(signatureData.lift().map { "Signatures (${it.count()})" })
|
||||||
|
|
||||||
|
val cellFactory = { node: StateNode ->
|
||||||
|
(node.state.value as? PartiallyResolvedTransaction.InputResolution.Resolved)?.run {
|
||||||
|
val data = stateAndRef.state.data
|
||||||
|
form {
|
||||||
|
label("${data.contract.javaClass.simpleName} (${stateAndRef.ref.toString().substring(0, 16)}...)[${stateAndRef.ref.index}]") {
|
||||||
|
graphic = imageview {
|
||||||
|
image = identicon(stateAndRef.ref.txhash, 10.0)
|
||||||
|
}
|
||||||
|
tooltip = identiconToolTip(stateAndRef.ref.txhash)
|
||||||
|
}
|
||||||
|
when (data) {
|
||||||
|
is Cash.State -> form {
|
||||||
|
fieldset {
|
||||||
|
field("Amount :") {
|
||||||
|
label(AmountFormatter.boring.format(data.amount.withoutIssuer()))
|
||||||
|
}
|
||||||
|
field("Issuer :") {
|
||||||
|
label("${data.amount.token.issuer}") {
|
||||||
|
tooltip(data.amount.token.issuer.party.owningKey.toStringShort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
field("Owner :") {
|
||||||
|
val owner = data.owner
|
||||||
|
val nodeInfo = Models.get<NetworkIdentityModel>(TransactionViewer::class).lookup(owner)
|
||||||
|
label(nodeInfo?.legalIdentity?.name ?: "???") {
|
||||||
|
tooltip(data.owner.toStringShort())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// TODO : Generic view using reflection?
|
||||||
|
else -> label {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} ?: label { text = "???" }
|
||||||
|
}
|
||||||
|
|
||||||
|
inputs.setCustomCellFactory(cellFactory)
|
||||||
|
outputs.setCustomCellFactory(cellFactory)
|
||||||
|
|
||||||
|
inputs.items = FXCollections.observableList(inputStates)
|
||||||
|
outputs.items = FXCollections.observableList(outputStates)
|
||||||
|
signatures.items = FXCollections.observableList(signatureData)
|
||||||
|
|
||||||
|
signatures.apply {
|
||||||
|
cellFormat { key ->
|
||||||
|
val nodeInfo = Models.get<NetworkIdentityModel>(TransactionViewer::class).lookup(key)
|
||||||
|
text = "${key.toStringShort()} (${nodeInfo?.legalIdentity?.name ?: "???"})"
|
||||||
|
}
|
||||||
|
prefHeight = 185.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash
|
* We calculate the total value by subtracting relevant input states and adding relevant output states, as long as they're cash
|
||||||
*/
|
*/
|
||||||
private fun calculateTotalEquiv(
|
private fun calculateTotalEquiv(identity: NodeInfo?,
|
||||||
identity: Party?,
|
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
|
||||||
reportingCurrencyExchange: Pair<Currency, (Amount<Currency>) -> Amount<Currency>>,
|
inputs: List<TransactionState<ContractState>>,
|
||||||
inputs: List<StateAndRef<ContractState>>?,
|
outputs: List<TransactionState<ContractState>>): AmountDiff<Currency> {
|
||||||
outputs: List<TransactionState<ContractState>>): AmountDiff<Currency>? {
|
|
||||||
if (inputs == null) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
var sum = 0L
|
|
||||||
val (reportingCurrency, exchange) = reportingCurrencyExchange
|
val (reportingCurrency, exchange) = reportingCurrencyExchange
|
||||||
val publicKey = identity?.owningKey
|
val publicKey = identity?.legalIdentity?.owningKey
|
||||||
inputs.forEach {
|
fun List<TransactionState<ContractState>>.sum(): Long {
|
||||||
val contractState = it.state.data
|
return this.map { it.data as? Cash.State }
|
||||||
if (contractState is Cash.State && publicKey == contractState.owner) {
|
.filterNotNull()
|
||||||
sum -= exchange(contractState.amount.withoutIssuer()).quantity
|
.filter { publicKey == it.owner }
|
||||||
}
|
.map { exchange(it.amount.withoutIssuer()).quantity }
|
||||||
}
|
.sum()
|
||||||
outputs.forEach {
|
|
||||||
val contractState = it.data
|
|
||||||
if (contractState is Cash.State && publicKey == contractState.owner) {
|
|
||||||
sum += exchange(contractState.amount.withoutIssuer()).quantity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return AmountDiff.fromLong(sum, reportingCurrency)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <A> resolvedOrDefault(
|
|
||||||
state: ObservableValue<PartiallyResolvedTransaction.InputResolution>,
|
|
||||||
default: A,
|
|
||||||
resolved: (StateAndRef<*>) -> A
|
|
||||||
): ObservableValue<A> {
|
|
||||||
return state.map {
|
|
||||||
when (it) {
|
|
||||||
is PartiallyResolvedTransaction.InputResolution.Unresolved -> default
|
|
||||||
is PartiallyResolvedTransaction.InputResolution.Resolved -> resolved(it.stateAndRef)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
return AmountDiff.fromLong(outputs.sum() - inputs.sum(), reportingCurrency)
|
||||||
}
|
}
|
@ -1,8 +1,8 @@
|
|||||||
#topLevel.root {
|
#topLevel.root {
|
||||||
-fx-background-image:url('../images/r3bg.png');
|
-fx-background-image: url('../images/r3bg.png');
|
||||||
-fx-background-size: cover;
|
-fx-background-size: cover;
|
||||||
-fx-background-repeat:no-repeat;
|
-fx-background-repeat: no-repeat;
|
||||||
-fx-base:white;
|
-fx-base: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
#cashViewer {
|
#cashViewer {
|
||||||
@ -17,29 +17,23 @@
|
|||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
.root {
|
.expand-row {
|
||||||
-fx-padding:5px;
|
-fx-padding: 10;
|
||||||
}
|
|
||||||
|
|
||||||
.root {
|
|
||||||
-fx-padding:5px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-pane {
|
.dialog-pane {
|
||||||
-fx-background-color:rgba(255,255,255,0.7);
|
-fx-background-color: rgba(255, 255, 255, 0.7);
|
||||||
-fx-background-radius:2px;
|
-fx-background-radius: 2px;
|
||||||
-fx-border-radius: 2px;
|
-fx-border-radius: 2px;
|
||||||
-fx-border-color: rgb(20,136,204);
|
-fx-border-color: rgb(20, 136, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.nested-column-header, .nested-column-header {
|
.nested-column-header, .nested-column-header {
|
||||||
-fx-background-color:transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-wrap-text:true;
|
-fx-wrap-text: true;
|
||||||
-fx-border-color:transparent;
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.text-field,
|
.text-field,
|
||||||
.table-column,
|
.table-column,
|
||||||
.label,
|
.label,
|
||||||
@ -48,28 +42,27 @@
|
|||||||
.button,
|
.button,
|
||||||
.split-menu-button,
|
.split-menu-button,
|
||||||
.choice-box {
|
.choice-box {
|
||||||
-fx-font-family:Effra;
|
-fx-font-family: Effra;
|
||||||
-fx-font-size:1em;
|
-fx-font-size: 1em;
|
||||||
-fx-text-fill:rgb(63,63,63);
|
-fx-text-fill: rgb(63, 63, 63);
|
||||||
-fx-font-smoothing-type: gray;
|
-fx-font-smoothing-type: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-highlight {
|
.text-highlight {
|
||||||
-fx-text-fill:rgb(20,136,204);
|
-fx-text-fill: rgb(20, 136, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
.context-menu {
|
.context-menu {
|
||||||
-fx-background-color:rgba(255,255,255,0.9);
|
-fx-background-color: rgba(255, 255, 255, 0.9);
|
||||||
}
|
}
|
||||||
|
|
||||||
.titled-pane .content,
|
.titled-pane .content,
|
||||||
.split-menu-button .label,
|
.split-menu-button .label,
|
||||||
.split-menu-button .arrow-button,
|
.split-menu-button .arrow-button,
|
||||||
.titled-pane .split-pane, .scroll-pane {
|
.titled-pane .split-pane, .scroll-pane {
|
||||||
-fx-background-color:transparent;
|
-fx-background-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.text-field,
|
.text-field,
|
||||||
.tree-table-view,
|
.tree-table-view,
|
||||||
.table-view,
|
.table-view,
|
||||||
@ -80,8 +73,8 @@
|
|||||||
.split-menu-button,
|
.split-menu-button,
|
||||||
.choice-box,
|
.choice-box,
|
||||||
.titled-pane .title {
|
.titled-pane .title {
|
||||||
-fx-border-color:rgb(150,150,150);
|
-fx-border-color: rgb(150, 150, 150);
|
||||||
-fx-border-width:1px;
|
-fx-border-width: 1px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -98,82 +91,116 @@
|
|||||||
.split-menu-button:hover,
|
.split-menu-button:hover,
|
||||||
.choice-box:hover,
|
.choice-box:hover,
|
||||||
.titled-pane:hover .title {
|
.titled-pane:hover .title {
|
||||||
-fx-border-color:rgb(20,136,204);
|
-fx-border-color: rgb(20, 136, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-menu-button:pressed,
|
.split-menu-button:pressed,
|
||||||
.button:pressed,
|
.button:pressed,
|
||||||
.choice-box:pressed,
|
.choice-box:pressed,
|
||||||
.titled-pane:expanded .title {
|
.titled-pane:expanded .title {
|
||||||
-fx-background-color:rgb(20,136,204);
|
-fx-background-color: rgb(20, 136, 204);
|
||||||
-fx-text-fill:white;
|
-fx-text-fill: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
-fx-padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-series-line {
|
||||||
|
-fx-stroke-width: 2px;
|
||||||
|
-fx-effect: null;
|
||||||
|
}
|
||||||
|
|
||||||
|
.default-color0.chart-series-line {
|
||||||
|
-fx-stroke: rgb(20, 136, 204);
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-plot-background {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-vertical-grid-lines {
|
||||||
|
-fx-stroke: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-horizontal-grid-lines {
|
||||||
|
-fx-stroke: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart-alternative-row-fill {
|
||||||
|
-fx-fill: transparent;
|
||||||
|
-fx-stroke: transparent;
|
||||||
|
-fx-stroke-width: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.titled-pane:expanded .title .text {
|
.titled-pane:expanded .title .text {
|
||||||
-fx-fill:white;
|
-fx-fill: white;
|
||||||
}
|
}
|
||||||
.titled-pane .title,.titled-pane .title:hover {
|
|
||||||
-fx-border-width:0.5px;
|
.titled-pane .title, .titled-pane .title:hover {
|
||||||
|
-fx-border-width: 0.5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text-field, .combo-box, .choice-box, .password-field {
|
.text-field, .combo-box, .choice-box, .password-field {
|
||||||
-fx-background-color:rgba(255,255,255,0.5);
|
-fx-background-color: rgba(255, 255, 255, 0.5);
|
||||||
-fx-background-radius:2px;
|
-fx-background-radius: 2px;
|
||||||
-fx-border-radius: 2px;
|
-fx-border-radius: 2px;
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* switch off highlighting for text-field when it's inside a combo-box */
|
/* switch off highlighting for text-field when it's inside a combo-box */
|
||||||
.combo-box .text-field, .combo-box .text-field:hover, .combo-box .text-field:focused {
|
.combo-box .text-field, .combo-box .text-field:hover, .combo-box .text-field:focused {
|
||||||
-fx-border-color:transparent;
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
/* table formatting */
|
|
||||||
|
/* table formatting */
|
||||||
|
|
||||||
.column-header-background,
|
.column-header-background,
|
||||||
.table-column,
|
.table-column,
|
||||||
.tree-table-row-cell, .column-header-background .filler {
|
.tree-table-row-cell, .column-header-background .filler {
|
||||||
-fx-background-color:transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-label-padding:3px;
|
-fx-label-padding: 3px;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
.nested-column-header .label {
|
||||||
|
-fx-wrap-text: true
|
||||||
|
}
|
||||||
|
|
||||||
.nested-column-header .label { -fx-wrap-text:true}
|
.table-column {
|
||||||
|
-fx-border-style: solid;
|
||||||
|
-fx-border-color: rgb(216, 216, 216); /*t r b l */
|
||||||
|
|
||||||
.table-column { -fx-border-style:solid;
|
-fx-border-width: 1px;
|
||||||
-fx-border-color:rgb(216,216,216); /*t r b l */
|
-fx-border-insets: 1px;
|
||||||
|
|
||||||
-fx-border-width:0.5px;
|
|
||||||
-fx-border-insets: 1.5px;
|
|
||||||
-fx-background-insets: 2px;
|
-fx-background-insets: 2px;
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
.tree-table-row-cell:even .table-column,
|
|
||||||
.table-row-cell:even .table-column,
|
|
||||||
.title, .split-menu-button, .button {
|
|
||||||
-fx-background-color: rgba(20,136,204,0.2);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row-cell:selected, .tree-table-row-cell:selected {
|
.tree-table-row-cell:even .table-column,
|
||||||
-fx-background-color:transparent;
|
.table-row-cell:even .table-column,
|
||||||
}
|
.title, .split-menu-button, .button {
|
||||||
.tree-table-row-cell:selected .table-column,
|
-fx-background-color: rgba(20, 136, 204, 0.2);
|
||||||
.tree-table-row-cell:focused .table-column,
|
}
|
||||||
.table-row-cell:selected .table-column,
|
|
||||||
.table-row-cell:focused .table-column {
|
|
||||||
-fx-background-color:rgba(20,136,204,0.8);
|
|
||||||
|
|
||||||
|
.table-row-cell:selected, .tree-table-row-cell:selected {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tree-table-row-cell:selected .table-column,
|
||||||
|
.tree-table-row-cell:focused .table-column,
|
||||||
|
.table-row-cell:selected .table-column,
|
||||||
|
.table-row-cell:focused .table-column {
|
||||||
|
-fx-background-color: rgba(20, 136, 204, 0.8);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.bad .text {
|
.bad .text {
|
||||||
-fx-fill:rgb(236,29,36);
|
-fx-fill: rgb(236, 29, 36);
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row-cell:focused .table-column .text,
|
.table-row-cell:focused .table-column .text,
|
||||||
.tree-table-row-cell:focused .table-column .text {
|
.tree-table-row-cell:focused .table-column .text {
|
||||||
-fx-fill:white;
|
-fx-fill: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-column:hover,
|
.table-column:hover,
|
||||||
@ -181,220 +208,239 @@
|
|||||||
.table-row-cell:hover .second-column,
|
.table-row-cell:hover .second-column,
|
||||||
.tree-table-row-cell:hover .first-column,
|
.tree-table-row-cell:hover .first-column,
|
||||||
.tree-table-row-cell:hover .second-column {
|
.tree-table-row-cell:hover .second-column {
|
||||||
-fx-border-color:rgb(20,136,204);
|
-fx-border-color: rgb(20, 136, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-table-view .column-header-background .nested-column-header .table-column,
|
.tree-table-view .column-header-background .nested-column-header .table-column,
|
||||||
.table-view .column-header-background .nested-column-header .table-column {
|
.table-view .column-header-background .nested-column-header .table-column {
|
||||||
-fx-border-color:transparent;
|
-fx-border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Special formatting - columns to be presented with no join between them */
|
/* Special formatting - columns to be presented with no join between them */
|
||||||
|
|
||||||
.first-column {
|
.first-column {
|
||||||
-fx-border-width:0.5px 0px 0.5px 0.5px;
|
-fx-border-width: 0.5px 0px 0.5px 0.5px;
|
||||||
-fx-border-insets: 1.5px 0px 1.5px 1.5px;
|
-fx-border-insets: 1.5px 0px 1.5px 1.5px;
|
||||||
-fx-background-insets: 2px 0px 2px 2px;
|
-fx-background-insets: 2px 0px 2px 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.second-column {
|
.second-column {
|
||||||
-fx-border-width:0.5px 0.5px 0.5px 0px;
|
-fx-border-width: 0.5px 0.5px 0.5px 0px;
|
||||||
-fx-border-insets: 1.5px 1.5px 1.5px 0px;
|
-fx-border-insets: 1.5px 1.5px 1.5px 0px;
|
||||||
-fx-background-insets: 2px 2px 2px 0px;
|
-fx-background-insets: 2px 2px 2px 0px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* highlighting where the user has typed a key */
|
/* highlighting where the user has typed a key */
|
||||||
.tree-table-view text-area, .table-view text-area{
|
.tree-table-view text-area, .table-view text-area {
|
||||||
-fx-font-weight:bold;
|
-fx-font-weight: bold;
|
||||||
-fx-fill:rgb(20,136,204);
|
-fx-fill: rgb(20, 136, 204);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-table-row-cell:selected .table-column text-area,
|
.tree-table-row-cell:selected .table-column text-area,
|
||||||
.table-row-cell:selected .table-column text-area
|
.table-row-cell:selected .table-column text-area {
|
||||||
{
|
-fx-font-weight: bold;
|
||||||
-fx-font-weight:bold;
|
-fx-fill: rgb(255, 255, 255);
|
||||||
-fx-fill:rgb(255,255,255);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* labels */
|
/* labels */
|
||||||
.dialog-pane .header-panel .label .text{
|
.dialog-pane .header-panel .label .text {
|
||||||
-fx-font-size:1em;
|
-fx-font-size: 1em;
|
||||||
-fx-fill:rgb(20,136,204);
|
-fx-fill: rgb(20, 136, 204);
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline, .headline {
|
#headline, .headline {
|
||||||
-fx-font-size:2.4em;
|
-fx-font-size: 2.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#subline, .subline {
|
#subline, .subline {
|
||||||
-fx-font-size:1.4em;
|
-fx-font-size: 1.4em;
|
||||||
}
|
}
|
||||||
|
|
||||||
#headline, #subline {
|
#headline, #subline {
|
||||||
-fx-text-fill:rgb(65,65,65);
|
-fx-text-fill: rgb(65, 65, 65);
|
||||||
-fx-padding:0px;
|
-fx-padding: 0px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* search boxes */
|
/* search boxes */
|
||||||
.search {
|
.search {
|
||||||
-fx-background-image:url('../images/search.png');
|
-fx-background-image: url('../images/search.png');
|
||||||
-fx-background-size:Auto 16px;
|
-fx-background-size: Auto 16px;
|
||||||
-fx-background-repeat:no-repeat;
|
-fx-background-repeat: no-repeat;
|
||||||
-fx-background-position:8px center;
|
-fx-background-position: 8px center;
|
||||||
-fx-padding:5px 5px 5px 30px;
|
-fx-padding: 5px 5px 5px 30px;
|
||||||
-fx-background-radius: 2px;
|
-fx-background-radius: 2px;
|
||||||
-fx-border-radius: 2px;
|
-fx-border-radius: 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-clear {
|
.search-clear {
|
||||||
-fx-image:url('../images/clear_inactive.png');
|
-fx-image: url('../images/clear_inactive.png');
|
||||||
|
-fx-padding: 5px 5px 5px 30px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.search-clear:hover {
|
.search-clear:hover {
|
||||||
-fx-image:url('../images/clear.png');
|
-fx-image: url('../images/clear.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.split-menu-button, .button, .choice-box {
|
.split-menu-button, .button, .choice-box {
|
||||||
-fx-background-radius:2px;
|
-fx-background-radius: 2px;
|
||||||
-fx-border-radius: 2px;
|
-fx-border-radius: 2px;
|
||||||
-fx-border-insets: 0.5px;
|
-fx-border-insets: 0.5px;
|
||||||
-fx-background-insets:0.5px;
|
-fx-background-insets: 0.5px;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tree-table-row-cell .monetary-value, .monetary-value .label, .table-row-cell .monetary-value {
|
.tree-table-row-cell .monetary-value, .monetary-value .label, .table-row-cell .monetary-value {
|
||||||
-fx-alignment:center-right;
|
-fx-alignment: center-right;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/* split panes */
|
/* split panes */
|
||||||
.split-pane-divider {
|
.split-pane-divider {
|
||||||
-fx-background-color: transparent;
|
-fx-background-color: transparent;
|
||||||
-fx-border-color: rgb(160,160,160);
|
-fx-border-color: rgb(160, 160, 160);
|
||||||
-fx-border-width: 0 0 0 0.5px
|
-fx-border-width: 0 0 0 0px;
|
||||||
|
-fx-padding: 0 0 0 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar {
|
||||||
|
-fx-background-color: #c3dbe9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-menu-item {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
-fx-max-width: infinity;
|
||||||
|
-fx-border-width: 0;
|
||||||
|
-fx-padding: 10, 10, 10, 10;
|
||||||
|
-fx-text-fill: white;
|
||||||
|
-fx-font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar-menu-item:hover, .sidebar-menu-item:selected {
|
||||||
|
-fx-background-color: #9edde9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-padding {
|
||||||
|
-fx-background-insets: 0;
|
||||||
|
-fx-padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Dashboard tiles */
|
/* Dashboard tiles */
|
||||||
|
.tile, .tile-user {
|
||||||
.tile,.tile-user {
|
-fx-padding: 10px;
|
||||||
-fx-padding: 10px;
|
-fx-pref-height: 250px;
|
||||||
-fx-pref-height:200px; -fx-pref-width:200px;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.corda-logo {
|
||||||
|
-fx-font-weight: bolder;
|
||||||
|
-fx-font-size: 1.8em;
|
||||||
|
-fx-padding: 5;
|
||||||
|
-fx-text-fill: rgb(20, 136, 204);
|
||||||
|
-fx-image: url("../images/corda-logo-alpha.png");
|
||||||
|
-fx-max-width: 30;
|
||||||
|
-fx-max-height: 30;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-pane > .viewport {
|
||||||
|
-fx-background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
.tile .title, .tile:expanded .title,
|
.tile .title, .tile:expanded .title,
|
||||||
.tile-user .title, .tile-user:expanded .title {
|
.tile-user .title, .tile-user:expanded .title {
|
||||||
-fx-alignment:center-right;
|
-fx-alignment: center-left;
|
||||||
-fx-font-size:1.4em;
|
-fx-font-size: 1.4em;
|
||||||
-fx-font-weight:bold;
|
-fx-font-weight: bold;
|
||||||
-fx-cursor:hand;
|
-fx-cursor: hand;
|
||||||
-fx-background-radius:2px 2px 0 0;
|
-fx-background-color: rgba(183, 210, 228, 0.2);
|
||||||
-fx-border-radius: 2px 2px 0 0;
|
-fx-border-color: transparent; /*t r b l */
|
||||||
-fx-border-width:1px 1px 0 1px;
|
|
||||||
-fx-background-color: rgba(255,255,255,0.5);
|
|
||||||
-fx-border-color:rgb(160,160,160); /*t r b l */
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile .title .text, .tile:expanded .title .text,
|
.tile .title .text, .tile:expanded .title .text,
|
||||||
.tile-user .title .text, .tile-user:expanded .title .text {
|
.tile-user .title .text, .tile-user:expanded .title .text {
|
||||||
-fx-fill:rgb(65,65,65);
|
-fx-fill: rgb(65, 65, 65);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile .content,
|
.tile .content,
|
||||||
.tile-user .content {
|
.tile-user .content {
|
||||||
-fx-background-color: rgba(255,255,255,0.7);
|
-fx-background-color: rgba(183, 210, 228, 0.2);
|
||||||
-fx-background-size:Auto 90%;
|
-fx-background-size: Auto 90%;
|
||||||
-fx-background-repeat:no-repeat;
|
-fx-background-repeat: no-repeat;
|
||||||
-fx-background-position:center center;
|
-fx-background-position: center center;
|
||||||
-fx-cursor:hand;
|
-fx-cursor: hand;
|
||||||
-fx-background-radius:0 0 2px 2px;
|
-fx-padding: 0px;
|
||||||
-fx-border-radius: 0 0 2px 2px;
|
-fx-alignment: bottom-right;
|
||||||
-fx-padding:0px;
|
-fx-border-color: transparent; /*t r b l */
|
||||||
-fx-alignment:bottom-right;
|
|
||||||
-fx-border-color:rgb(150,150,150); /*t r b l */
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile .label,
|
.tile .label,
|
||||||
.tile-user .label {
|
.tile-user .label {
|
||||||
-fx-font-size:2.4em;
|
-fx-font-size: 2.4em;
|
||||||
-fx-padding:20px;
|
-fx-padding: 20px;
|
||||||
-fx-text-fill:rgb(65,65,65);
|
-fx-text-fill: rgb(65, 65, 65);
|
||||||
-fx-font-weight:normal;
|
-fx-font-weight: normal;
|
||||||
-fx-text-alignment:right;
|
-fx-text-alignment: right;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile:hover .label,
|
.tile:hover,
|
||||||
.tile-user:hover .label {
|
|
||||||
-fx-padding:24px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile:hover .content, .tile:hover .title,
|
|
||||||
.tile-user:hover .content, .tile-user:hover .title {
|
.tile-user:hover .content, .tile-user:hover .title {
|
||||||
-fx-border-color:rgb(20,136,204);
|
-fx-border-color: rgb(20, 136, 204);
|
||||||
-fx-background-color: rgb(20,136,204);
|
-fx-border-width: 2;
|
||||||
|
|
||||||
}
|
|
||||||
.tile:hover, .tile-user:hover {
|
|
||||||
-fx-padding:4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tile:hover .label, .tile:hover .label .text, .tile:hover .title .text {
|
|
||||||
-fx-text-fill:rgb(255,255,255);
|
|
||||||
-fx-fill:rgb(255,255,255);
|
|
||||||
-fx-font-weight:bold;
|
|
||||||
-fx-effect:none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#tile_cash .content {
|
#tile_cash .content {
|
||||||
-fx-background-image:url('../images/cash_lrg.png');
|
-fx-background-image: url('../images/cash_lrg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
#tile_debtors .content {
|
#tile_debtors .content {
|
||||||
-fx-background-image:url('../images/outflow_lrg.png');
|
-fx-background-image: url('../images/outflow_lrg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
#tile_creditors .content {
|
#tile_creditors .content {
|
||||||
-fx-background-image:url('../images/inflow_lrg.png');
|
-fx-background-image: url('../images/inflow_lrg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
#tile_tx .content {
|
#tile_tx .content {
|
||||||
-fx-background-image:url('../images/tx_lrg.png');
|
-fx-background-image: url('../images/tx_lrg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
#tile_cpty .content {
|
#tile_cpty .content {
|
||||||
-fx-background-image:url('../images/cpty_lrg.png');
|
-fx-background-image: url('../images/cpty_lrg.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-user .content {
|
.tile-user .content {
|
||||||
-fx-background-image:url('../images/user_b.png');
|
-fx-background-image: url('../images/user_b.png');
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-user-test-man .content {
|
.tile-user-test-man .content {
|
||||||
-fx-background-image:url('../images/man1.png');
|
-fx-background-image: url('../images/man1.png');
|
||||||
-fx-background-size:cover;
|
-fx-background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-user-test-woman .content {
|
.tile-user-test-woman .content {
|
||||||
-fx-background-image:url('../images/woman1.png');
|
-fx-background-image: url('../images/woman1.png');
|
||||||
-fx-background-size:cover;
|
-fx-background-size: cover;
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-user .label {
|
.tile-user .label {
|
||||||
-fx-background-color:rgba(255,255,255,0.7);
|
-fx-background-color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.tile-user:hover .title, .tile-user:hover .content {
|
.tile-user:hover .title, .tile-user:hover .content {
|
||||||
-fx-background-color:rgba(255,255,255,0.7);
|
-fx-background-color: rgba(255, 255, 255, 0.7);
|
||||||
}
|
}
|
||||||
|
|
||||||
.counterparty {
|
.counterparty {
|
||||||
-fx-background-image:url('../images/inst_128.png');
|
-fx-background-image: url('../images/inst_128.png');
|
||||||
-fx-background-size:Auto 16px;
|
-fx-background-size: Auto 16px;
|
||||||
-fx-background-repeat: no-repeat;
|
-fx-background-repeat: no-repeat;
|
||||||
-fx-background-position:0px center;
|
-fx-background-position: 0px center;
|
||||||
-fx-padding:0 0 0 20px;
|
-fx-padding: 0 0 0 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.state-panel{
|
.state-panel {
|
||||||
-fx-background-color: rgba(255,255,255,0.7);
|
-fx-background-color: rgba(255, 255, 255, 0.7);
|
||||||
-fx-border-color:rgb(150,150,150);
|
-fx-border-color: rgb(150, 150, 150);
|
||||||
-fx-insets:5px
|
-fx-insets: 5px
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,17 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<GridPane vgap="5" hgap="5" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<Label text="State ID"/>
|
||||||
|
<Label text="Issuer" GridPane.rowIndex="1"/>
|
||||||
|
<Label text="Originated" wrapText="true" GridPane.rowIndex="2"/>
|
||||||
|
<Label text="Amount" wrapText="true" GridPane.rowIndex="3"/>
|
||||||
|
<Label fx:id="equivLabel" text="USD" wrapText="true" GridPane.rowIndex="4"/>
|
||||||
|
|
||||||
<HBox spacing="5.0" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<Label fx:id="stateIdValueLabel" text="39043-329090-390091" GridPane.columnIndex="1"/>
|
||||||
<children>
|
<Label fx:id="issuerValueLabel" styleClass="counterparty" text="C-03820 HSBC GROUP PLC" GridPane.columnIndex="1" GridPane.rowIndex="1"/>
|
||||||
<VBox HBox.hgrow="SOMETIMES">
|
<Label fx:id="originatedValueLabel" text="2018-04-27 11:34 UTC" GridPane.columnIndex="1" GridPane.rowIndex="2"/>
|
||||||
<children>
|
<Label fx:id="amountValueLabel" text="GBP 0.00" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="3"/>
|
||||||
<Label text="State ID" VBox.vgrow="ALWAYS" />
|
<Label fx:id="equivValueLabel" text="0.00" wrapText="true" GridPane.columnIndex="1" GridPane.rowIndex="4"/>
|
||||||
<Label text="Issuer" />
|
</GridPane>
|
||||||
<Label text="Originated" wrapText="true" />
|
|
||||||
<Label text="Amount" wrapText="true" />
|
|
||||||
<Label fx:id="equivLabel" text="USD" wrapText="true" />
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
<VBox HBox.hgrow="ALWAYS">
|
|
||||||
<children>
|
|
||||||
<Label fx:id="stateIdValueLabel" text="39043-329090-390091" />
|
|
||||||
<Label fx:id="issuerValueLabel" styleClass="counterparty" text="C-03820 HSBC GROUP PLC" />
|
|
||||||
<Label fx:id="originatedValueLabel" text="2018-04-27 11:34 UTC" />
|
|
||||||
<Label fx:id="amountValueLabel" text="GBP 0.00" wrapText="true" />
|
|
||||||
<Label fx:id="equivValueLabel" text="0.00" wrapText="true" />
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
|
@ -1,84 +1,29 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import java.lang.String?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.layout.*?>
|
||||||
<?import javafx.scene.control.ListView?>
|
<BorderPane stylesheets="@../css/wallet.css" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<?import javafx.scene.control.SplitPane?>
|
<padding>
|
||||||
<?import javafx.scene.control.TextField?>
|
<Insets right="10" left="5" bottom="5" top="5"/>
|
||||||
<?import javafx.scene.control.TreeTableColumn?>
|
</padding>
|
||||||
<?import javafx.scene.control.TreeTableView?>
|
<center>
|
||||||
<?import javafx.scene.image.Image?>
|
<SplitPane fx:id="splitPane" dividerPositions="0.5">
|
||||||
<?import javafx.scene.image.ImageView?>
|
<VBox fx:id="leftPane" spacing="5.0">
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<TreeTableView fx:id="cashViewerTable" showRoot="false" VBox.vgrow="ALWAYS">
|
||||||
<?import javafx.scene.layout.VBox?>
|
<columns>
|
||||||
|
<TreeTableColumn fx:id="cashViewerTableIssuerCurrency" styleClass="first-column" text="Issuer/Currency"/>
|
||||||
<SplitPane fx:id="topSplitPane" dividerPositions="0.5" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<TreeTableColumn fx:id="cashViewerTableLocalCurrency" text="Local currency" styleClass="monetary-value, second-column"/>
|
||||||
<items>
|
<TreeTableColumn fx:id="cashViewerTableEquiv" styleClass="monetary-value" text="Equiv"/>
|
||||||
<VBox fx:id="leftPane" spacing="5.0" styleClass="root">
|
</columns>
|
||||||
<children>
|
</TreeTableView>
|
||||||
<StackPane alignment="CENTER_RIGHT">
|
<Label fx:id="totalMatchingLabel" text="Total 15 matching issuer(s)"/>
|
||||||
<VBox.margin>
|
</VBox>
|
||||||
<Insets />
|
<VBox fx:id="rightPane" spacing="5.0">
|
||||||
</VBox.margin>
|
<Button fx:id="toggleButton" mnemonicParsing="false" text=">>"/>
|
||||||
<children>
|
<ListView fx:id="cashStatesList" VBox.vgrow="ALWAYS"/>
|
||||||
<TextField id="search" fx:id="searchCriteriaTextField" promptText="Search by issuer/currency" styleClass="search">
|
<Label fx:id="totalPositionsLabel" text="Total 18 position(s)"/>
|
||||||
<opaqueInsets>
|
</VBox>
|
||||||
<Insets />
|
</SplitPane>
|
||||||
</opaqueInsets>
|
</center>
|
||||||
<StackPane.margin>
|
</BorderPane>
|
||||||
<Insets />
|
|
||||||
</StackPane.margin>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</TextField>
|
|
||||||
<ImageView fx:id="searchCancelImageView" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/clear_inactive.png" />
|
|
||||||
</image>
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</StackPane.margin>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
|
||||||
</StackPane>
|
|
||||||
<Label fx:id="totalMatchingLabel" alignment="BOTTOM_LEFT" text="Total 15 matching issuer(s)" wrapText="true">
|
|
||||||
<VBox.margin>
|
|
||||||
<Insets bottom="5.0" right="10.0" top="5.0" />
|
|
||||||
</VBox.margin>
|
|
||||||
</Label>
|
|
||||||
<TreeTableView fx:id="cashViewerTable" showRoot="false" VBox.vgrow="ALWAYS">
|
|
||||||
<columns>
|
|
||||||
<TreeTableColumn fx:id="cashViewerTableIssuerCurrency" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="100.0" styleClass="first-column" text="Issuer/Currency" />
|
|
||||||
<TreeTableColumn fx:id="cashViewerTableLocalCurrency" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="132.0" text="Local currency">
|
|
||||||
<styleClass>
|
|
||||||
<String fx:value="monetary-value" />
|
|
||||||
<String fx:value="second-column" />
|
|
||||||
</styleClass>
|
|
||||||
</TreeTableColumn>
|
|
||||||
<TreeTableColumn fx:id="cashViewerTableEquiv" maxWidth="1.7976931348623157E308" minWidth="-1.0" prefWidth="72.0" styleClass="monetary-value" text="Equiv" />
|
|
||||||
</columns>
|
|
||||||
</TreeTableView>
|
|
||||||
</children>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</VBox>
|
|
||||||
<VBox fx:id="rightPane" spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<Button mnemonicParsing="false" text=">>" />
|
|
||||||
<Label fx:id="totalPositionsLabel" styleClass="subline" text="Total 18 position(s)" />
|
|
||||||
<Label fx:id="equivSumLabel" styleClass="headline" text="USD 394.6k" />
|
|
||||||
<ListView fx:id="cashStatesList" VBox.vgrow="ALWAYS" />
|
|
||||||
</children>
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets />
|
|
||||||
</opaqueInsets>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</VBox>
|
|
||||||
</items>
|
|
||||||
</SplitPane>
|
|
||||||
|
@ -0,0 +1,34 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<GridPane hgap="10" styleClass="expand-row" stylesheets="@../css/wallet.css" vgap="10" xmlns="http://javafx.com/javafx/8.0.112-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<TitledPane fx:id="inputPane" collapsible="false" text="Input" GridPane.fillWidth="true">
|
||||||
|
<ListView fx:id="inputs"/>
|
||||||
|
</TitledPane>
|
||||||
|
|
||||||
|
<Label GridPane.columnIndex="1">
|
||||||
|
<graphic>
|
||||||
|
<FontAwesomeIconView glyphName="PLAY" glyphSize="40" style="-fx-fill: rgb(20, 136, 204);"/>
|
||||||
|
</graphic>
|
||||||
|
</Label>
|
||||||
|
|
||||||
|
<TitledPane fx:id="outputPane" collapsible="false" text="Outputs" GridPane.columnIndex="2">
|
||||||
|
<ListView fx:id="outputs" maxWidth="Infinity"/>
|
||||||
|
</TitledPane>
|
||||||
|
|
||||||
|
<TitledPane fx:id="signaturesPane" collapsible="false" text="Signatures" GridPane.columnSpan="3" GridPane.rowIndex="1">
|
||||||
|
<ListView fx:id="signatures"/>
|
||||||
|
</TitledPane>
|
||||||
|
|
||||||
|
<columnConstraints>
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"/>
|
||||||
|
<ColumnConstraints/>
|
||||||
|
<ColumnConstraints hgrow="ALWAYS"/>
|
||||||
|
</columnConstraints>
|
||||||
|
<rowConstraints>
|
||||||
|
<RowConstraints vgrow="ALWAYS"/>
|
||||||
|
<RowConstraints/>
|
||||||
|
</rowConstraints>
|
||||||
|
</GridPane>
|
@ -1,95 +1,45 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Button?>
|
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.MenuItem?>
|
<?import javafx.scene.control.MenuItem?>
|
||||||
<?import javafx.scene.control.SplitMenuButton?>
|
<?import javafx.scene.control.SplitMenuButton?>
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.image.Image?>
|
<?import javafx.scene.image.Image?>
|
||||||
<?import javafx.scene.image.ImageView?>
|
<?import javafx.scene.image.ImageView?>
|
||||||
<?import javafx.scene.layout.HBox?>
|
<?import javafx.scene.layout.GridPane?>
|
||||||
<?import javafx.scene.layout.StackPane?>
|
<GridPane hgap="10" stylesheets="@../css/wallet.css" styleClass="header-panel" vgap="5" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<?import javafx.scene.layout.VBox?>
|
<padding>
|
||||||
|
<Insets left="10.0" right="10.0" top="5.0" bottom="5.0"/>
|
||||||
|
</padding>
|
||||||
|
<!-- Row 1 -->
|
||||||
|
<Label id="headline" fx:id="sectionLabel" alignment="BOTTOM_CENTER" maxHeight="Infinity" text="Home" GridPane.columnIndex="0" GridPane.hgrow="ALWAYS"/>
|
||||||
|
|
||||||
<VBox xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<SplitMenuButton fx:id="userButton" maxHeight="Infinity" mnemonicParsing="false" text="DRUTTER" GridPane.columnIndex="3">
|
||||||
<children>
|
<items>
|
||||||
<VBox spacing="5.0">
|
<MenuItem mnemonicParsing="false" text="Sign out"/>
|
||||||
<children>
|
<MenuItem mnemonicParsing="false" text="Account settings..."/>
|
||||||
<HBox>
|
</items>
|
||||||
<children>
|
<graphic>
|
||||||
<HBox alignment="CENTER_LEFT" spacing="5.0" HBox.hgrow="ALWAYS">
|
<ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
|
||||||
<children>
|
<Image url="@../images/user_w.png"/>
|
||||||
<VBox fx:id="sectionIconContainer" alignment="CENTER">
|
</ImageView>
|
||||||
<children>
|
</graphic>
|
||||||
<ImageView fx:id="sectionIcon" fitHeight="30.0" fitWidth="30.0" pickOnBounds="true" preserveRatio="true">
|
</SplitMenuButton>
|
||||||
<image>
|
<!--<Button fx:id="settingsButton" maxHeight="Infinity" mnemonicParsing="false" text="Settings" GridPane.columnIndex="4">
|
||||||
<Image url="@../../images/home.png" />
|
<graphic>
|
||||||
</image>
|
<ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
|
||||||
</ImageView>
|
<Image url="@../images/settings_w.png"/>
|
||||||
</children>
|
</ImageView>
|
||||||
</VBox>
|
</graphic>
|
||||||
<Label id="headline" fx:id="sectionLabel" text="Home" />
|
</Button>-->
|
||||||
</children>
|
|
||||||
<padding>
|
<!--<!– Row 2 –>
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
<StackPane alignment="CENTER_RIGHT" GridPane.columnSpan="5" GridPane.rowIndex="1">
|
||||||
</padding>
|
<TextField fx:id="search_main" promptText="Search for states, transactions, counterparties etc." styleClass="search"/>
|
||||||
</HBox>
|
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear">
|
||||||
<HBox alignment="TOP_RIGHT" spacing="5.0">
|
<StackPane.margin>
|
||||||
<children>
|
<Insets right="10.0"/>
|
||||||
<Button fx:id="debugNextButton" mnemonicParsing="false" text="Next" />
|
</StackPane.margin>
|
||||||
<Button fx:id="debugGoStopButton" mnemonicParsing="false" text="!!!" />
|
</ImageView>
|
||||||
<SplitMenuButton mnemonicParsing="false" text="DRUTTER">
|
</StackPane>-->
|
||||||
<items>
|
</GridPane>
|
||||||
<MenuItem mnemonicParsing="false" text="Sign out" />
|
|
||||||
<MenuItem mnemonicParsing="false" text="Account settings..." />
|
|
||||||
</items>
|
|
||||||
<graphic>
|
|
||||||
<ImageView fitHeight="20.0" fitWidth="52.0" pickOnBounds="true" preserveRatio="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/user_w.png" />
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</graphic>
|
|
||||||
</SplitMenuButton>
|
|
||||||
<Button fx:id="settingsButton" mnemonicParsing="false" text="Settings">
|
|
||||||
<graphic>
|
|
||||||
<ImageView fitHeight="20.0" fitWidth="20.0" pickOnBounds="true" preserveRatio="true">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/settings_w.png" />
|
|
||||||
</image>
|
|
||||||
</ImageView>
|
|
||||||
</graphic>
|
|
||||||
</Button>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<StackPane alignment="CENTER_RIGHT">
|
|
||||||
<children>
|
|
||||||
<TextField id="search_main" promptText="Search for states, transactions, counterparties etc." styleClass="search">
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets />
|
|
||||||
</opaqueInsets>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="30.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</TextField>
|
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/clear_inactive.png" />
|
|
||||||
</image>
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</StackPane.margin>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
|
||||||
</StackPane>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
<StackPane alignment="CENTER_RIGHT" />
|
|
||||||
</children>
|
|
||||||
<padding>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</padding>
|
|
||||||
</VBox>
|
|
||||||
|
@ -2,19 +2,21 @@
|
|||||||
|
|
||||||
<?import javafx.scene.control.*?>
|
<?import javafx.scene.control.*?>
|
||||||
<?import javafx.scene.layout.TilePane?>
|
<?import javafx.scene.layout.TilePane?>
|
||||||
<TilePane prefHeight="425.0" prefWidth="425.0" tileAlignment="TOP_LEFT" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<ScrollPane hbarPolicy="NEVER" fitToWidth="true" stylesheets="@../css/wallet.css" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<TitledPane id="tile_cash" fx:id="ourCashPane" alignment="CENTER" collapsible="false" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our cash">
|
<TilePane fx:id="tilePane" tileAlignment="TOP_LEFT" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<Label fx:id="ourCashLabel" text="USD 186.7m" textAlignment="CENTER" wrapText="true"/>
|
<TitledPane id="Cash" fx:id="ourCashPane" collapsible="false" onMouseClicked="#changeView" styleClass="tile" text="Our cash">
|
||||||
</TitledPane>
|
<Label fx:id="ourCashLabel" text="USD 186.7m" textAlignment="CENTER" wrapText="true" />
|
||||||
<TitledPane id="tile_debtors" fx:id="ourDebtorsPane" alignment="CENTER" collapsible="false" layoutX="232.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our debtors">
|
</TitledPane>
|
||||||
<Label text="USD 71.3m" textAlignment="CENTER" wrapText="true"/>
|
<TitledPane id="tile_debtors" fx:id="ourDebtorsPane" collapsible="false" styleClass="tile" text="Our debtors">
|
||||||
</TitledPane>
|
<Label text="USD 71.3m" textAlignment="CENTER" wrapText="true" />
|
||||||
<TitledPane id="tile_creditors" fx:id="ourCreditorsPane" alignment="CENTER" collapsible="false" layoutX="312.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our creditors">
|
</TitledPane>
|
||||||
<Label text="USD (29.4m)" textAlignment="CENTER" wrapText="true"/>
|
<TitledPane id="tile_creditors" fx:id="ourCreditorsPane" collapsible="false" styleClass="tile" text="Our creditors">
|
||||||
</TitledPane>
|
<Label text="USD (29.4m)" textAlignment="CENTER" wrapText="true" />
|
||||||
<TitledPane id="tile_tx" fx:id="ourTransactionsPane" alignment="CENTER" collapsible="false" layoutX="392.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="Our transactions">
|
</TitledPane>
|
||||||
<Label fx:id="ourTransactionsLabel" text="In flight: 1,315" textAlignment="CENTER" wrapText="true"/>
|
<TitledPane id="Transaction" fx:id="ourTransactionsPane" collapsible="false" onMouseClicked="#changeView" styleClass="tile" text="Our transactions">
|
||||||
</TitledPane>
|
<Label fx:id="ourTransactionsLabel" textAlignment="CENTER" wrapText="true" />
|
||||||
<TitledPane id="tile_new_tx" fx:id="newTransaction" alignment="CENTER" collapsible="false" layoutX="472.0" layoutY="10.0" prefHeight="160.0" prefWidth="160.0" styleClass="tile" text="New Transaction">
|
</TitledPane>
|
||||||
</TitledPane>
|
<TitledPane id="NewTransaction" fx:id="newTransaction" collapsible="false" onMouseClicked="#changeView" styleClass="tile" text="New Transaction">
|
||||||
</TilePane>
|
</TitledPane>
|
||||||
|
</TilePane>
|
||||||
|
</ScrollPane>
|
||||||
|
@ -0,0 +1,24 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.*?>
|
||||||
|
<?import javafx.scene.layout.*?>
|
||||||
|
<DialogPane stylesheets="@../css/wallet.css" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<padding>
|
||||||
|
<Insets top="10" bottom="10" left="50" right="50"/>
|
||||||
|
</padding>
|
||||||
|
<content>
|
||||||
|
<GridPane hgap="10" vgap="10" prefWidth="400">
|
||||||
|
<Label text="Corda Node :"/>
|
||||||
|
<TextField fx:id="host" promptText="Host" GridPane.columnIndex="1"/>
|
||||||
|
<TextField fx:id="port" promptText="Port" prefWidth="100" GridPane.columnIndex="2"/>
|
||||||
|
|
||||||
|
<Label text="Username :" GridPane.rowIndex="1"/>
|
||||||
|
<TextField fx:id="username" promptText="Username" GridPane.rowIndex="1" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
|
||||||
|
|
||||||
|
<Label text="Password:" GridPane.rowIndex="2"/>
|
||||||
|
<PasswordField fx:id="password" promptText="Password" GridPane.rowIndex="2" GridPane.columnIndex="1" GridPane.columnSpan="2"/>
|
||||||
|
</GridPane>
|
||||||
|
</content>
|
||||||
|
<ButtonType fx:id="connectButton" text="Connect" buttonData="OK_DONE"/>
|
||||||
|
</DialogPane>
|
@ -0,0 +1,23 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.geometry.Insets?>
|
||||||
|
<?import javafx.scene.control.TextField?>
|
||||||
|
<?import javafx.scene.image.Image?>
|
||||||
|
<?import javafx.scene.image.ImageView?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
|
<StackPane alignment="CENTER_RIGHT" stylesheets="@../css/wallet.css" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<TextField fx:id="textField" promptText="Filter transactions by originator, contract type..." styleClass="search">
|
||||||
|
<padding>
|
||||||
|
<Insets bottom="5.0" left="30.0" right="5.0"/>
|
||||||
|
</padding>
|
||||||
|
<StackPane.margin>
|
||||||
|
<Insets bottom="5.0" top="5.0"/>
|
||||||
|
</StackPane.margin>
|
||||||
|
</TextField>
|
||||||
|
<ImageView fx:id="clearButton" fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear">
|
||||||
|
<StackPane.margin>
|
||||||
|
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
|
||||||
|
</StackPane.margin>
|
||||||
|
<Image url="@../images/clear_inactive.png"/>
|
||||||
|
</ImageView>
|
||||||
|
</StackPane>
|
@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.Label?>
|
||||||
|
<?import javafx.scene.control.Separator?>
|
||||||
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<VBox xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
|
<Label styleClass="corda-logo"/>
|
||||||
|
<Separator/>
|
||||||
|
</VBox>
|
@ -1,8 +1,12 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
|
<?import javafx.scene.control.SplitPane?>
|
||||||
<?import javafx.scene.layout.BorderPane?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
|
<?import javafx.scene.layout.StackPane?>
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<StackPane stylesheets="@../css/wallet.css" prefHeight="650" prefWidth="900" styleClass="root, no-padding" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<VBox fx:id="topLevel" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<SplitPane dividerPositions="0.0" styleClass="no-padding, split-pane-divider">
|
||||||
<BorderPane fx:id="selectionBorderPane"/>
|
<VBox fx:id="sidebarPane" maxWidth="200.0" minWidth="80" styleClass="sidebar" SplitPane.resizableWithParent="false"/>
|
||||||
</VBox>
|
<BorderPane fx:id="selectionBorderPane" maxHeight="Infinity" minWidth="400"/>
|
||||||
|
</SplitPane>
|
||||||
|
</StackPane>
|
@ -1,137 +1,22 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
|
||||||
<?import java.lang.String?>
|
|
||||||
<?import javafx.geometry.Insets?>
|
<?import javafx.geometry.Insets?>
|
||||||
<?import javafx.scene.control.Label?>
|
<?import javafx.scene.control.Label?>
|
||||||
<?import javafx.scene.control.ListView?>
|
|
||||||
<?import javafx.scene.control.SplitPane?>
|
|
||||||
<?import javafx.scene.control.TableColumn?>
|
|
||||||
<?import javafx.scene.control.TableView?>
|
<?import javafx.scene.control.TableView?>
|
||||||
<?import javafx.scene.control.TextField?>
|
<?import javafx.scene.layout.BorderPane?>
|
||||||
<?import javafx.scene.control.TitledPane?>
|
|
||||||
<?import javafx.scene.image.Image?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
|
||||||
<?import javafx.scene.layout.HBox?>
|
|
||||||
<?import javafx.scene.layout.StackPane?>
|
|
||||||
<?import javafx.scene.layout.VBox?>
|
<?import javafx.scene.layout.VBox?>
|
||||||
|
<BorderPane stylesheets="@../css/wallet.css" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
||||||
<VBox styleClass="view" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
<padding>
|
||||||
<children>
|
<Insets top="5" left="5" right="10" bottom="5"/>
|
||||||
<StackPane alignment="CENTER_RIGHT">
|
</padding>
|
||||||
<children>
|
<center>
|
||||||
<TextField promptText="Filter transactions by originator, contract type..." styleClass="search">
|
<TableView fx:id="transactionViewTable" VBox.vgrow="ALWAYS">
|
||||||
<opaqueInsets>
|
<columnResizePolicy>
|
||||||
<Insets />
|
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY"/>
|
||||||
</opaqueInsets>
|
</columnResizePolicy>
|
||||||
<padding>
|
</TableView>
|
||||||
<Insets bottom="5.0" left="30.0" right="5.0" top="5.0" />
|
</center>
|
||||||
</padding>
|
<bottom>
|
||||||
<StackPane.margin>
|
<Label fx:id="matchingTransactionsLabel" text="matching transaction(s)"/>
|
||||||
<Insets bottom="5.0" top="5.0" />
|
</bottom>
|
||||||
</StackPane.margin>
|
</BorderPane>
|
||||||
</TextField>
|
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../../../../../../../../internal/explorer/src/main/resources/images/clear_inactive.png" />
|
|
||||||
</image>
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</StackPane.margin>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
|
||||||
</StackPane>
|
|
||||||
<SplitPane fx:id="topSplitPane" dividerPositions="0.3, 0.6" orientation="VERTICAL" prefHeight="562.0" prefWidth="1087.0" VBox.vgrow="ALWAYS">
|
|
||||||
<items>
|
|
||||||
<TableView fx:id="transactionViewTable" prefHeight="200.0" prefWidth="200.0">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="transactionViewTransactionId" prefWidth="75.0" text="Transaction ID" />
|
|
||||||
<TableColumn fx:id="transactionViewStateMachineId" prefWidth="187.0" text="StateMachine ID" />
|
|
||||||
<TableColumn fx:id="transactionViewClientUuid" prefWidth="75.0" text="Client UUID" />
|
|
||||||
<TableColumn fx:id="transactionViewTransactionStatus" prefWidth="75.0" text="Transaction status" />
|
|
||||||
<TableColumn fx:id="transactionViewProtocolStatus" prefWidth="75.0" text="Protocol status" />
|
|
||||||
<TableColumn fx:id="transactionViewStateMachineStatus" prefWidth="75.0" text="SM Status" />
|
|
||||||
<TableColumn fx:id="transactionViewCommandTypes" prefWidth="75.0" text="Command type(s)" />
|
|
||||||
<TableColumn fx:id="transactionViewTotalValueEquiv" prefWidth="75.0" styleClass="monetary-value" text="Total value (USD equiv)" />
|
|
||||||
</columns>
|
|
||||||
<columnResizePolicy>
|
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
<TitledPane fx:id="contractStatesTitledPane" animated="false" text="Contract states">
|
|
||||||
<content>
|
|
||||||
<SplitPane dividerPositions="0.5" prefHeight="160.0" prefWidth="200.0">
|
|
||||||
<items>
|
|
||||||
<VBox prefHeight="200.0" prefWidth="100.0">
|
|
||||||
<children>
|
|
||||||
<HBox spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<Label text="Inputs:" />
|
|
||||||
<Label fx:id="contractStatesInputsCountLabel" text="Label" />
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<TableView fx:id="contractStatesInputStatesTable" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesId" prefWidth="75.0" text="ID" />
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesType" prefWidth="75.0" text="Type" />
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesOwner" prefWidth="75.0" text="Owner" />
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesLocalCurrency" prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesAmount" prefWidth="75.0" text="Amount">
|
|
||||||
<styleClass>
|
|
||||||
<String fx:value="second-column" />
|
|
||||||
<String fx:value="monetary-value" />
|
|
||||||
</styleClass>
|
|
||||||
</TableColumn>
|
|
||||||
<TableColumn fx:id="contractStatesInputStatesEquiv" prefWidth="75.0" text="USD Equiv" />
|
|
||||||
</columns>
|
|
||||||
<columnResizePolicy>
|
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
<VBox prefHeight="200.0" prefWidth="100.0">
|
|
||||||
<children>
|
|
||||||
<HBox spacing="5.0">
|
|
||||||
<children>
|
|
||||||
<Label text="Outputs:" />
|
|
||||||
<Label fx:id="contractStatesOutputsCountLabel" text="Label" />
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
<TableView fx:id="contractStatesOutputStatesTable" prefHeight="200.0" prefWidth="200.0" VBox.vgrow="ALWAYS">
|
|
||||||
<columns>
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesId" prefWidth="75.0" text="ID" />
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesType" prefWidth="75.0" text="Type" />
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesOwner" prefWidth="75.0" text="Owner" />
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesLocalCurrency" prefWidth="75.0" styleClass="first-column" text="Local Ccy" />
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesAmount" prefWidth="75.0" text="Amount">
|
|
||||||
<styleClass>
|
|
||||||
<String fx:value="second-column" />
|
|
||||||
<String fx:value="monetary-value" />
|
|
||||||
</styleClass>
|
|
||||||
</TableColumn>
|
|
||||||
<TableColumn fx:id="contractStatesOutputStatesEquiv" prefWidth="75.0" text="USD Equiv" />
|
|
||||||
</columns>
|
|
||||||
<columnResizePolicy>
|
|
||||||
<TableView fx:constant="CONSTRAINED_RESIZE_POLICY" />
|
|
||||||
</columnResizePolicy>
|
|
||||||
</TableView>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
</items>
|
|
||||||
</SplitPane>
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
<TitledPane fx:id="signaturesTitledPane" animated="false" text="Signatures">
|
|
||||||
<content>
|
|
||||||
<ListView fx:id="signaturesList" />
|
|
||||||
</content>
|
|
||||||
</TitledPane>
|
|
||||||
</items>
|
|
||||||
</SplitPane>
|
|
||||||
<HBox>
|
|
||||||
<children>
|
|
||||||
<Label fx:id="matchingTransactionsLabel" text="matching transaction(s)" />
|
|
||||||
</children>
|
|
||||||
</HBox>
|
|
||||||
</children>
|
|
||||||
</VBox>
|
|
||||||
|
@ -1,28 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
|
||||||
|
|
||||||
<?import javafx.geometry.Insets?>
|
|
||||||
<?import javafx.scene.control.TextField?>
|
|
||||||
<?import javafx.scene.image.Image?>
|
|
||||||
<?import javafx.scene.image.ImageView?>
|
|
||||||
<?import javafx.scene.layout.StackPane?>
|
|
||||||
|
|
||||||
<StackPane alignment="CENTER_RIGHT" xmlns="http://javafx.com/javafx/8.0.76-ea" xmlns:fx="http://javafx.com/fxml/1">
|
|
||||||
<children>
|
|
||||||
<TextField fx:id="SearchCriteriaTextField" promptText="Set prompt text" styleClass="search">
|
|
||||||
<opaqueInsets>
|
|
||||||
<Insets />
|
|
||||||
</opaqueInsets>
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets />
|
|
||||||
</StackPane.margin>
|
|
||||||
</TextField>
|
|
||||||
<ImageView fitHeight="16.0" fitWidth="16.0" pickOnBounds="true" preserveRatio="true" styleClass="search-clear" StackPane.alignment="CENTER_RIGHT">
|
|
||||||
<image>
|
|
||||||
<Image url="@../../images/clear_inactive.png" />
|
|
||||||
</image>
|
|
||||||
<StackPane.margin>
|
|
||||||
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0" />
|
|
||||||
</StackPane.margin>
|
|
||||||
</ImageView>
|
|
||||||
</children>
|
|
||||||
</StackPane>
|
|
@ -77,6 +77,10 @@ class CordaRPCOpsImpl(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun nodeIdentity(): NodeInfo {
|
||||||
|
return services.myInfo
|
||||||
|
}
|
||||||
|
|
||||||
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
|
override fun addVaultTransactionNote(txnId: SecureHash, txnNote: String) {
|
||||||
return databaseTransaction(database) {
|
return databaseTransaction(database) {
|
||||||
services.vaultService.addNoteToTransaction(txnId, txnNote)
|
services.vaultService.addNoteToTransaction(txnId, txnNote)
|
||||||
|
@ -118,6 +118,10 @@ interface CordaRPCOps : RPCOps {
|
|||||||
*/
|
*/
|
||||||
fun executeCommand(command: ClientToServiceCommand): TransactionBuildResult
|
fun executeCommand(command: ClientToServiceCommand): TransactionBuildResult
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns Node's identity, assuming this will not change while the node is running.
|
||||||
|
*/
|
||||||
|
fun nodeIdentity(): NodeInfo
|
||||||
/*
|
/*
|
||||||
* Add note(s) to an existing Vault transaction
|
* Add note(s) to an existing Vault transaction
|
||||||
*/
|
*/
|
||||||
|
@ -16,10 +16,8 @@ import com.r3corda.core.crypto.*
|
|||||||
import com.r3corda.core.node.NodeInfo
|
import com.r3corda.core.node.NodeInfo
|
||||||
import com.r3corda.core.node.PhysicalLocation
|
import com.r3corda.core.node.PhysicalLocation
|
||||||
import com.r3corda.core.node.ServiceEntry
|
import com.r3corda.core.node.ServiceEntry
|
||||||
import com.r3corda.core.node.services.NetworkMapCache
|
import com.r3corda.core.node.WorldCoordinate
|
||||||
import com.r3corda.core.node.services.ServiceInfo
|
import com.r3corda.core.node.services.*
|
||||||
import com.r3corda.core.node.services.StateMachineTransactionMapping
|
|
||||||
import com.r3corda.core.node.services.Vault
|
|
||||||
import com.r3corda.core.protocols.StateMachineRunId
|
import com.r3corda.core.protocols.StateMachineRunId
|
||||||
import com.r3corda.core.serialization.*
|
import com.r3corda.core.serialization.*
|
||||||
import com.r3corda.core.transactions.SignedTransaction
|
import com.r3corda.core.transactions.SignedTransaction
|
||||||
@ -101,6 +99,7 @@ fun requirePermission(permission: String) {
|
|||||||
*/
|
*/
|
||||||
open class RPCException(msg: String, cause: Throwable?) : RuntimeException(msg, cause) {
|
open class RPCException(msg: String, cause: Throwable?) : RuntimeException(msg, cause) {
|
||||||
constructor(msg: String) : this(msg, null)
|
constructor(msg: String) : this(msg, null)
|
||||||
|
|
||||||
class DeadlineExceeded(rpcName: String) : RPCException("Deadline exceeded on call to $rpcName")
|
class DeadlineExceeded(rpcName: String) : RPCException("Deadline exceeded on call to $rpcName")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,8 +186,12 @@ private class RPCKryo(observableSerializer: Serializer<Observable<Any>>? = null)
|
|||||||
kryo.writeObject(output, nodeAddress.hostAndPort)
|
kryo.writeObject(output, nodeAddress.hostAndPort)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
register(NodeMessagingClient.makeNetworkMapAddress(HostAndPort.fromString("localhost:0")).javaClass)
|
||||||
|
register(ServiceInfo::class.java)
|
||||||
|
register(ServiceType.getServiceType("ab", "ab").javaClass)
|
||||||
|
register(ServiceType.parse("ab").javaClass)
|
||||||
|
register(WorldCoordinate::class.java)
|
||||||
register(HostAndPort::class.java)
|
register(HostAndPort::class.java)
|
||||||
register(ServiceInfo::class.java, read = { kryo, input -> ServiceInfo.parse(input.readString()) }, write = Kryo::writeObject)
|
|
||||||
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
// Exceptions. We don't bother sending the stack traces as the client will fill in its own anyway.
|
||||||
register(IllegalArgumentException::class.java)
|
register(IllegalArgumentException::class.java)
|
||||||
// Kryo couldn't serialize Collections.unmodifiableCollection in Throwable correctly, causing null pointer exception when try to access the deserialize object.
|
// Kryo couldn't serialize Collections.unmodifiableCollection in Throwable correctly, causing null pointer exception when try to access the deserialize object.
|
||||||
|
@ -25,7 +25,6 @@ import com.r3corda.node.services.network.NetworkMapService.Companion.FETCH_PROTO
|
|||||||
import com.r3corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
import com.r3corda.node.services.network.NetworkMapService.Companion.SUBSCRIPTION_PROTOCOL_TOPIC
|
||||||
import com.r3corda.node.services.network.NetworkMapService.FetchMapResponse
|
import com.r3corda.node.services.network.NetworkMapService.FetchMapResponse
|
||||||
import com.r3corda.node.services.network.NetworkMapService.SubscribeResponse
|
import com.r3corda.node.services.network.NetworkMapService.SubscribeResponse
|
||||||
import com.r3corda.node.services.transactions.SimpleNotaryService
|
|
||||||
import com.r3corda.node.utilities.AddOrRemove
|
import com.r3corda.node.utilities.AddOrRemove
|
||||||
import com.r3corda.protocols.sendRequest
|
import com.r3corda.protocols.sendRequest
|
||||||
import rx.Observable
|
import rx.Observable
|
||||||
@ -59,13 +58,7 @@ open class InMemoryNetworkMapCache : SingletonSerializeAsToken(), NetworkMapCach
|
|||||||
|
|
||||||
override fun track(): Pair<List<NodeInfo>, Observable<MapChange>> {
|
override fun track(): Pair<List<NodeInfo>, Observable<MapChange>> {
|
||||||
synchronized(_changed) {
|
synchronized(_changed) {
|
||||||
fun NodeInfo.isCordaService(): Boolean {
|
return Pair(partyNodes, _changed.bufferUntilSubscribed())
|
||||||
return advertisedServices.any { it.info.type in setOf(SimpleNotaryService.type, NetworkMapService.type) }
|
|
||||||
}
|
|
||||||
|
|
||||||
val currentParties = partyNodes.filter { !it.isCordaService() }
|
|
||||||
val changes = changed.filter { !it.node.isCordaService() }
|
|
||||||
return Pair(currentParties, changes.bufferUntilSubscribed())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
package com.r3corda.node.services
|
package com.r3corda.node.services
|
||||||
|
|
||||||
import com.google.common.net.HostAndPort
|
import com.google.common.net.HostAndPort
|
||||||
|
import com.r3corda.core.contracts.ClientToServiceCommand
|
||||||
|
import com.r3corda.core.contracts.ContractState
|
||||||
|
import com.r3corda.core.contracts.StateAndRef
|
||||||
|
import com.r3corda.core.crypto.Party
|
||||||
|
import com.r3corda.core.crypto.SecureHash
|
||||||
import com.r3corda.core.crypto.generateKeyPair
|
import com.r3corda.core.crypto.generateKeyPair
|
||||||
import com.r3corda.core.messaging.Message
|
import com.r3corda.core.messaging.Message
|
||||||
import com.r3corda.core.messaging.createMessage
|
import com.r3corda.core.messaging.createMessage
|
||||||
|
Reference in New Issue
Block a user