Add counting of different flows to the widget. Change widget styling, add some icons.

This commit is contained in:
Katarzyna Streich 2017-05-19 11:25:56 +01:00
parent 775c26c573
commit 67a417389b
8 changed files with 98 additions and 54 deletions

View File

@ -1,11 +1,13 @@
package net.corda.client.jfx.model package net.corda.client.jfx.model
import javafx.beans.property.SimpleIntegerProperty
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.beans.value.ObservableValue import javafx.beans.value.ObservableValue
import javafx.collections.FXCollections import javafx.collections.FXCollections
import net.corda.client.jfx.utils.LeftOuterJoinedMap import net.corda.client.jfx.utils.LeftOuterJoinedMap
import net.corda.client.jfx.utils.fold import net.corda.client.jfx.utils.fold
import net.corda.client.jfx.utils.getObservableValues import net.corda.client.jfx.utils.getObservableValues
import net.corda.client.jfx.utils.lift
import net.corda.client.jfx.utils.recordAsAssociation import net.corda.client.jfx.utils.recordAsAssociation
import net.corda.core.ErrorOr import net.corda.core.ErrorOr
import net.corda.core.flows.FlowInitiator import net.corda.core.flows.FlowInitiator
@ -36,18 +38,35 @@ data class StateMachineData(
val id: StateMachineRunId, val id: StateMachineRunId,
val stateMachineName: String, val stateMachineName: String,
val flowInitiator: FlowInitiator, val flowInitiator: FlowInitiator,
val addRmStatus: ObservableValue<StateMachineStatus>, val smmStatus: Pair<ObservableValue<StateMachineStatus>, ObservableValue<ProgressStatus>>
val stateMachineStatus: ObservableValue<ProgressStatus>
) )
data class Counter(
var errored: SimpleIntegerProperty = SimpleIntegerProperty(0),
var success: SimpleIntegerProperty = SimpleIntegerProperty(0),
var progress: SimpleIntegerProperty = SimpleIntegerProperty(0)
) {
fun addSmm() { progress.value += 1 }
fun removeSmm(result: ErrorOr<*>) {
progress.value -= 1
when (result.error) {
null -> success.value += 1
else -> errored.value += 1
}
}
}
class StateMachineDataModel { class StateMachineDataModel {
private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates) private val stateMachineUpdates by observable(NodeMonitorModel::stateMachineUpdates)
private val progressTracking by observable(NodeMonitorModel::progressTracking) private val progressTracking by observable(NodeMonitorModel::progressTracking)
private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId) private val progressEvents = progressTracking.recordAsAssociation(ProgressTrackingEvent::stateMachineId)
val counter = Counter()
private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update -> private val stateMachineStatus = stateMachineUpdates.fold(FXCollections.observableHashMap<StateMachineRunId, SimpleObjectProperty<StateMachineStatus>>()) { map, update ->
when (update) { when (update) {
is StateMachineUpdate.Added -> { is StateMachineUpdate.Added -> {
counter.addSmm()
val flowInitiator= update.stateMachineInfo.initiator val flowInitiator= update.stateMachineInfo.initiator
val added: SimpleObjectProperty<StateMachineStatus> = val added: SimpleObjectProperty<StateMachineStatus> =
SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName, flowInitiator)) SimpleObjectProperty(StateMachineStatus.Added(update.stateMachineInfo.flowLogicClassName, flowInitiator))
@ -56,6 +75,7 @@ class StateMachineDataModel {
is StateMachineUpdate.Removed -> { is StateMachineUpdate.Removed -> {
val added = map[update.id] val added = map[update.id]
added ?: throw Exception("State machine removed with unknown id ${update.id}") added ?: throw Exception("State machine removed with unknown id ${update.id}")
counter.removeSmm(update.result)
added.set(StateMachineStatus.Removed(update.result)) added.set(StateMachineStatus.Removed(update.result))
} }
} }
@ -63,9 +83,12 @@ class StateMachineDataModel {
private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress -> private val stateMachineDataList = LeftOuterJoinedMap(stateMachineStatus, progressEvents) { id, status, progress ->
val smStatus = status.value as StateMachineStatus.Added val smStatus = status.value as StateMachineStatus.Added
StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator, status, StateMachineData(id, smStatus.stateMachineName, smStatus.flowInitiator,
EasyBind.map(progress) { ProgressStatus(it?.message) }) Pair(status, EasyBind.map(progress) { ProgressStatus(it?.message) }))
}.getObservableValues() }.getObservableValues()
val stateMachinesAll = stateMachineDataList val stateMachinesAll = stateMachineDataList
val error = counter.errored
val success = counter.success
val progress = counter.progress
} }

View File

@ -11,46 +11,16 @@ import jfxtras.resources.JFXtrasFontRoboto
import joptsimple.OptionParser import joptsimple.OptionParser
import net.corda.client.jfx.model.Models import net.corda.client.jfx.model.Models
import net.corda.client.jfx.model.observableValue import net.corda.client.jfx.model.observableValue
import net.corda.client.mock.EventGenerator
import net.corda.client.mock.Generator
import net.corda.client.mock.pickOne
import net.corda.contracts.asset.Cash
import net.corda.core.contracts.Amount
import net.corda.core.contracts.GBP
import net.corda.core.contracts.USD
import net.corda.core.failure
import net.corda.core.messaging.FlowHandle
import net.corda.core.node.services.ServiceInfo
import net.corda.core.node.services.ServiceType
import net.corda.core.serialization.OpaqueBytes
import net.corda.core.success
import net.corda.core.transactions.SignedTransaction
import net.corda.core.utilities.ALICE
import net.corda.core.utilities.BOB
import net.corda.core.utilities.DUMMY_NOTARY
import net.corda.core.utilities.loggerFor import net.corda.core.utilities.loggerFor
import net.corda.explorer.model.CordaViewModel import net.corda.explorer.model.CordaViewModel
import net.corda.explorer.model.SettingsModel import net.corda.explorer.model.SettingsModel
import net.corda.explorer.views.* import net.corda.explorer.views.*
import net.corda.explorer.views.cordapps.cash.CashViewer import net.corda.explorer.views.cordapps.cash.CashViewer
import net.corda.flows.CashExitFlow
import net.corda.flows.CashFlowCommand
import net.corda.flows.CashIssueFlow
import net.corda.flows.CashPaymentFlow
import net.corda.flows.IssuerFlow.IssuanceRequester
import net.corda.node.driver.PortAllocation
import net.corda.node.driver.driver
import net.corda.node.services.startFlowPermission
import net.corda.node.services.transactions.SimpleNotaryService
import net.corda.nodeapi.User
import org.apache.commons.lang.SystemUtils import org.apache.commons.lang.SystemUtils
import org.bouncycastle.asn1.x500.X500Name
import org.controlsfx.dialog.ExceptionDialog import org.controlsfx.dialog.ExceptionDialog
import tornadofx.App import tornadofx.App
import tornadofx.addStageIcon import tornadofx.addStageIcon
import tornadofx.find import tornadofx.find
import java.time.Instant
import java.util.*
/** /**
* Main class for Explorer, you will need Tornado FX to run the explorer. * Main class for Explorer, you will need Tornado FX to run the explorer.

View File

@ -4,6 +4,7 @@ import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import javafx.beans.property.SimpleObjectProperty import javafx.beans.property.SimpleObjectProperty
import javafx.collections.ObservableList import javafx.collections.ObservableList
import javafx.scene.Node import javafx.scene.Node
import javafx.scene.control.Label
import tornadofx.* import tornadofx.*
class CordaViewModel { class CordaViewModel {
@ -31,4 +32,4 @@ abstract class CordaView(title: String? = null) : View(title) {
} }
} }
data class CordaWidget(val name: String, val node: Node) data class CordaWidget(val name: String, val node: Node, val icon: FontAwesomeIcon? = null)

View File

@ -1,6 +1,7 @@
package net.corda.explorer.views package net.corda.explorer.views
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon import de.jensd.fx.glyphs.fontawesome.FontAwesomeIcon
import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.collections.ObservableList import javafx.collections.ObservableList
import javafx.scene.Node import javafx.scene.Node
@ -46,6 +47,7 @@ class Dashboard : CordaView() {
selectedView.value = view selectedView.value = view
} }
} }
it.icon?.let { graphic = FontAwesomeIconView(it).apply { glyphSize = 30.0 } }
} }
} }
} }

View File

@ -5,10 +5,11 @@ import de.jensd.fx.glyphs.fontawesome.FontAwesomeIconView
import javafx.beans.binding.Bindings import javafx.beans.binding.Bindings
import javafx.collections.ObservableList import javafx.collections.ObservableList
import javafx.geometry.HPos import javafx.geometry.HPos
import javafx.geometry.Pos import javafx.geometry.Insets
import javafx.scene.Parent import javafx.scene.Parent
import javafx.scene.control.Label import javafx.scene.control.Label
import javafx.scene.control.TableView import javafx.scene.control.TableView
import javafx.scene.input.MouseButton
import javafx.scene.layout.BorderPane import javafx.scene.layout.BorderPane
import javafx.scene.layout.GridPane import javafx.scene.layout.GridPane
import javafx.scene.layout.VBox import javafx.scene.layout.VBox
@ -18,7 +19,8 @@ import net.corda.client.jfx.model.StateMachineData
import net.corda.client.jfx.model.StateMachineDataModel import net.corda.client.jfx.model.StateMachineDataModel
import net.corda.client.jfx.model.StateMachineStatus import net.corda.client.jfx.model.StateMachineStatus
import net.corda.client.jfx.model.observableList import net.corda.client.jfx.model.observableList
import net.corda.client.jfx.model.observableListReadOnly import net.corda.client.jfx.model.observableValue
import net.corda.client.jfx.model.writableValue
import net.corda.client.jfx.utils.map import net.corda.client.jfx.utils.map
import net.corda.core.crypto.SecureHash import net.corda.core.crypto.SecureHash
import net.corda.core.crypto.toBase58String import net.corda.core.crypto.toBase58String
@ -26,34 +28,61 @@ import net.corda.core.flows.FlowInitiator
import net.corda.core.transactions.SignedTransaction import net.corda.core.transactions.SignedTransaction
import net.corda.explorer.formatters.FlowInitiatorFormatter import net.corda.explorer.formatters.FlowInitiatorFormatter
import net.corda.explorer.formatters.FlowNameFormatter import net.corda.explorer.formatters.FlowNameFormatter
import net.corda.explorer.formatters.PartyNameFormatter
import net.corda.explorer.identicon.identicon import net.corda.explorer.identicon.identicon
import net.corda.explorer.identicon.identiconToolTip import net.corda.explorer.identicon.identiconToolTip
import net.corda.explorer.model.CordaView import net.corda.explorer.model.CordaView
import net.corda.explorer.model.CordaViewModel
import net.corda.explorer.model.CordaWidget import net.corda.explorer.model.CordaWidget
import net.corda.explorer.ui.setCustomCellFactory import net.corda.explorer.ui.setCustomCellFactory
import tornadofx.* import tornadofx.*
// TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows). // TODO Rethink whole idea of showing communication as table, it should be tree view for each StateMachine (with subflows).
class StateMachineViewer : CordaView("Flow Triage") { class StateMachineViewer : CordaView("Flow Triage") {
override val root by fxml<BorderPane>() override val root by fxml<BorderPane>()
override val icon = FontAwesomeIcon.HEARTBEAT override val icon = FontAwesomeIcon.HEARTBEAT
override val widgets = listOf(CordaWidget(title, StateMachineViewer.StateMachineWidget())).observable() override val widgets = listOf(CordaWidget(title, StateMachineWidget(), icon)).observable()
private val allViewTable by fxid<TableView<StateMachineData>>() private val allViewTable by fxid<TableView<StateMachineData>>()
private val matchingFlowsLabel by fxid<Label>() private val matchingFlowsLabel by fxid<Label>()
private val selectedView by writableValue(CordaViewModel::selectedView)
private val stateMachinesAll by observableList(StateMachineDataModel::stateMachinesAll) private val stateMachinesAll by observableList(StateMachineDataModel::stateMachinesAll)
private class StateMachineWidget : BorderPane() { inner private class StateMachineWidget : GridPane() {
private val stateMachinesAll by observableListReadOnly(StateMachineDataModel::stateMachinesAll) private val error by observableValue(StateMachineDataModel::error)
private val success by observableValue(StateMachineDataModel::success)
private val progress by observableValue(StateMachineDataModel::progress)
init { init {
right { padding = Insets(0.0, 5.0, 10.0, 10.0)
label { hgap = 5.0
textProperty().bind(Bindings.size(stateMachinesAll).map(Number::toString)) styleClass += "chart-plot-background"
BorderPane.setAlignment(this, Pos.BOTTOM_RIGHT) row {
label { makeIconLabel(this, FontAwesomeIcon.CHECK, "", "-fx-fill: lightslategrey", 30.0) }
label { textProperty().bind(success.map(Number::toString)) }
}
row {
label { makeIconLabel(this, FontAwesomeIcon.BOLT, "", "-fx-fill: lightslategrey", 30.0) }
label { textProperty().bind(error.map(Number::toString)) }
}
row {
label { makeIconLabel(this, FontAwesomeIcon.ROCKET, "", "-fx-fill: lightslategrey", 30.0) }
label { textProperty().bind(progress.map(Number::toString)) }
} }
} }
} }
fun makeIconLabel(labelNode: Label, icon: FontAwesomeIcon, initText: String, customStyle: String? = null, iconSize: Double = 15.0) {
labelNode.apply {
graphic = FontAwesomeIconView(icon).apply {
glyphSize = iconSize
textAlignment = TextAlignment.LEFT
style = customStyle
}
text = initText
}
} }
fun makeColumns(table: TableView<StateMachineData>, tableItems: ObservableList<StateMachineData>, withResult: Boolean = true) { fun makeColumns(table: TableView<StateMachineData>, tableItems: ObservableList<StateMachineData>, withResult: Boolean = true) {

View File

@ -53,7 +53,11 @@ class TransactionViewer : CordaView("Transactions") {
private val reportingCurrency by observableValue(ReportingCurrencyModel::reportingCurrency) private val reportingCurrency by observableValue(ReportingCurrencyModel::reportingCurrency)
private val myIdentity by observableValue(NetworkIdentityModel::myIdentity) private val myIdentity by observableValue(NetworkIdentityModel::myIdentity)
override val widgets = listOf(CordaWidget(title, TransactionWidget())).observable() override val widgets = listOf(CordaWidget(title, TransactionWidget(), icon)).observable()
private var scrollPosition: Int = 0
private lateinit var expander: ExpanderColumn<TransactionViewer.Transaction>
var txIdToScroll: SecureHash? = null // Passed as param.
/** /**
* 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

View File

@ -46,7 +46,7 @@ class CashViewer : CordaView("Cash") {
override val root: BorderPane by fxml() override val root: BorderPane by fxml()
override val icon: FontAwesomeIcon = FontAwesomeIcon.MONEY override val icon: FontAwesomeIcon = FontAwesomeIcon.MONEY
// View's widget. // View's widget.
override val widgets = listOf(CordaWidget("Treasury", CashWidget())).observable() override val widgets = listOf(CordaWidget("Treasury", CashWidget(), icon)).observable()
// Left pane // Left pane
private val leftPane: VBox by fxid() private val leftPane: VBox by fxid()
private val splitPane: SplitPane by fxid() private val splitPane: SplitPane by fxid()

View File

@ -111,6 +111,7 @@
-fx-cursor: hand; -fx-cursor: hand;
-fx-background-color: -color-1; -fx-background-color: -color-1;
-fx-border-color: transparent; -fx-border-color: transparent;
-fx-border-radius: 2;
} }
.tile .title .text, .tile:expanded .title .text { .tile .title .text, .tile:expanded .title .text {
@ -123,14 +124,15 @@
-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-padding: 0px; -fx-padding: 5px;
-fx-alignment: bottom-right; -fx-alignment: bottom-right;
-fx-border-color: transparent; /*t r b l */ -fx-border-color: transparent; /*t r b l */
-fx-border-radius: 2;
} }
.tile .label { .tile .label {
-fx-font-size: 2.4em; -fx-font-size: 2.0em;
-fx-padding: 20px; -fx-padding: 10px;
-fx-text-fill: -color-0; -fx-text-fill: -color-0;
-fx-font-weight: normal; -fx-font-weight: normal;
-fx-text-alignment: right; -fx-text-alignment: right;
@ -271,9 +273,22 @@
-fx-stroke: red; -fx-stroke: red;
} }
/* Flow details */ /* Other */
.smm-detail-grid { .identicon {
-fx-border-color: -color-3; -fx-border-radius: 2;
-fx-border-width: 1; }
.flow-expanded {
-fx-background-color: rgba(255, 255, 255, 0.5);
-fx-background-size: Auto 90%;
-fx-background-repeat: no-repeat;
-fx-background-position: center center;
-fx-border-color: transparent;
-fx-border-radius: 2;
}
.flow-expanded:hover {
-fx-border-color: -color-3;
-fx-border-width: 2;
-fx-border-radius: 2; -fx-border-radius: 2;
} }