Skip to content
Snippets Groups Projects
Unverified Commit bf533b56 authored by Angelo Capossele's avatar Angelo Capossele Committed by GitHub
Browse files

Create view on the dashboard showing information about an address (#553)

* :sparkles: Add address explorer

* :sparkles: Add inclusion state as Status

* :lipstick: Add conflicting and spent status

* :rotating_light: Fix linter warnings

* :package: Update packr dashboard

* :bug: Fix missing 404

* :poop: Replace Trunk and Branch with Parents

* :sparkles: Add Available balance and solidification time

* :package: Update packr

* :pencil2: Fix typo
parent 24525698
No related branches found
No related tags found
No related merge requests found
......@@ -5,4 +5,4 @@
// and any other packr generated files.
package dashboard
import _ "github.com/iotaledger/goshimmer/plugins/analysis/dashboard/packrd"
import _ "github.com/iotaledger/goshimmer/plugins/dashboard/packrd"
......@@ -18,7 +18,7 @@ module.exports = {
},
output: {
path: outPath,
publicPath: isProduction ? "/app" : "http://192.168.1.215:9090/",
publicPath: isProduction ? "/app" : "http://127.0.0.1:9090/",
filename: isProduction ? '[contenthash].js' : '[hash].js',
chunkFilename: isProduction ? '[name].[contenthash].js' : '[name].[hash].js'
},
......
This diff is collapsed.
......@@ -5,8 +5,12 @@ import (
"net/http"
"sync"
"github.com/iotaledger/goshimmer/dapps/valuetransfers"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/address"
"github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/tangle"
"github.com/iotaledger/goshimmer/packages/binary/messagelayer/message"
"github.com/iotaledger/goshimmer/plugins/messagelayer"
"github.com/iotaledger/goshimmer/plugins/webapi/value/utils"
"github.com/labstack/echo"
)
......@@ -60,8 +64,17 @@ func createExplorerMessage(msg *message.Message) (*ExplorerMessage, error) {
// ExplorerAddress defines the struct of the ExplorerAddress.
type ExplorerAddress struct {
// Messages hold the list of *ExplorerMessage.
Messages []*ExplorerMessage `json:"message"`
Address string `json:"address"`
OutputIDs []ExplorerOutput `json:"output_ids"`
}
// ExplorerOutput defines the struct of the ExplorerOutput.
type ExplorerOutput struct {
ID string `json:"id"`
Balances []utils.Balance `json:"balances"`
InclusionState utils.InclusionState `json:"inclusion_state"`
SolidificationTime int64 `json:"solidification_time"`
ConsumerCount int `json:"consumer_count"`
}
// SearchResult defines the struct of the SearchResult.
......@@ -98,34 +111,40 @@ func setupExplorerRoutes(routeGroup *echo.Group) {
routeGroup.GET("/search/:search", func(c echo.Context) error {
search := c.Param("search")
result := &SearchResult{}
wg := sync.WaitGroup{}
if len(search) < 81 {
switch len(search) {
case address.Length:
wg.Add(1)
go func() {
defer wg.Done()
addr, err := findAddress(search)
if err == nil {
result.Address = addr
}
}()
case message.IdLength:
wg.Add(1)
go func() {
defer wg.Done()
messageID, err := message.NewId(search)
if err != nil {
return
}
msg, err := findMessage(messageID)
if err == nil {
result.Message = msg
}
}()
default:
return fmt.Errorf("%w: search ID %s", ErrInvalidParameter, search)
}
wg := sync.WaitGroup{}
wg.Add(2)
go func() {
defer wg.Done()
messageID, err := message.NewId(search)
if err != nil {
return
}
msg, err := findMessage(messageID)
if err == nil {
result.Message = msg
}
}()
go func() {
defer wg.Done()
addr, err := findAddress(search)
if err == nil {
result.Address = addr
}
}()
wg.Wait()
return c.JSON(http.StatusOK, result)
......@@ -142,8 +161,56 @@ func findMessage(messageID message.Id) (explorerMsg *ExplorerMessage, err error)
return
}
func findAddress(address string) (*ExplorerAddress, error) {
return nil, fmt.Errorf("%w: address %s", ErrNotFound, address)
func findAddress(strAddress string) (*ExplorerAddress, error) {
address, err := address.FromBase58(strAddress)
if err != nil {
return nil, fmt.Errorf("%w: address %s", ErrNotFound, strAddress)
}
outputids := make([]ExplorerOutput, 0)
// get outputids by address
for id, cachedOutput := range valuetransfers.Tangle().OutputsOnAddress(address) {
cachedOutput.Consume(func(output *tangle.Output) {
// iterate balances
var b []utils.Balance
for _, balance := range output.Balances() {
b = append(b, utils.Balance{
Value: balance.Value,
Color: balance.Color.String(),
})
}
valuetransfers.Tangle().TransactionMetadata(output.TransactionID()).Consume(func(txMeta *tangle.TransactionMetadata) {
inclusionState := utils.InclusionState{}
inclusionState.Confirmed = txMeta.Confirmed()
inclusionState.Liked = txMeta.Liked()
inclusionState.Rejected = txMeta.Rejected()
inclusionState.Finalized = txMeta.Finalized()
inclusionState.Conflicting = txMeta.Conflicting()
inclusionState.Confirmed = txMeta.Confirmed()
outputids = append(outputids, ExplorerOutput{
ID: id.String(),
Balances: b,
InclusionState: inclusionState,
ConsumerCount: output.ConsumerCount(),
SolidificationTime: txMeta.SolidificationTime().Unix(),
})
})
})
}
if len(outputids) == 0 {
return nil, fmt.Errorf("%w: address %s", ErrNotFound, strAddress)
}
return &ExplorerAddress{
Address: strAddress,
OutputIDs: outputids,
}, nil
// TODO: ADD ADDRESS LOOKUPS ONCE THE VALUE TRANSFER ONTOLOGY IS MERGED
}
......@@ -7,9 +7,8 @@ import {inject, observer} from "mobx-react";
import ExplorerStore from "app/stores/ExplorerStore";
import Spinner from "react-bootstrap/Spinner";
import ListGroup from "react-bootstrap/ListGroup";
import {Link} from 'react-router-dom';
import * as dateformat from 'dateformat';
import Alert from "react-bootstrap/Alert";
import * as dateformat from 'dateformat';
interface Props {
nodeStore?: NodeStore;
......@@ -40,24 +39,104 @@ export class ExplorerAddressQueryResult extends React.Component<Props, any> {
render() {
let {id} = this.props.match.params;
let {addr, query_loading} = this.props.explorerStore;
let msgsEle = [];
let {addr, query_loading, query_err} = this.props.explorerStore;
let outputs = [];
let available_balances = [];
let total_balance = new Map();
let get_balances = function (balances) {
if (balances.length == 0) {
return "empty";
}
return balances;
}
if (query_err) {
return (
<Container>
<h3>Address not available - 404</h3>
<p>
Address {id} not found.
</p>
</Container>
);
}
if (addr) {
for (let i = 0; i < addr.messages.length; i++) {
let msg = addr.messages[i];
msgsEle.push(
<ListGroup.Item key={msg.id}>
for (let i = 0; i < addr.output_ids.length; i++) {
let output = addr.output_ids[i];
let consumed = "Spent: ";
let conflicting = "Conflicting: false";
if (output.consumer_count) {
consumed += "true";
if (output.consumer_count > 1) {
conflicting = "Conflicting: true";
}
} else {
consumed += "false";
}
let status = "Status: ";
if (output.inclusion_state.confirmed) {
status += ' confirmed ';
} else if (output.inclusion_state.rejected) {
status += ' rejected ';
} else {
status += ' pending ';
}
let balances = [];
for (let j=0; j < addr.output_ids[i].balances.length; j++) {
let balance = addr.output_ids[i].balances[j]
let oldBalance = 0;
if (total_balance.has(balance.color)) {
oldBalance = total_balance.get(balance.color);
}
if (addr.output_ids[i].consumer_count == 0 && addr.output_ids[i].inclusion_state.confirmed) {
total_balance.set(balance.color, balance.value + oldBalance);
}
balances.push(
<ListGroup.Item key={balance.color}>
<small>
{'Color:'} {balance.color} {' Value:'} {balance.value}
</small>
</ListGroup.Item>
)
}
outputs.push(
<ListGroup.Item key={output.id}>
<small>
{dateformat(new Date(msg.solidification_timestamp * 1000), "dd.mm.yyyy HH:MM:ss")} {' '}
<Link to={`/explorer/message/${msg.id}`}>{msg.id}</Link>
{'Output ID:'} {output.id} {' '}
<br></br>
Solidification Time: {dateformat(new Date(output.solidification_time * 1000), "dd.mm.yyyy HH:MM:ss")}
<br></br>
{status}
<br></br>
{consumed}
<br></br>
{conflicting}
<br></br>
{'Balance:'} {balances}
</small>
</ListGroup.Item>
);
}
total_balance.forEach((balance: number, color: string) => {
available_balances.push(
<ListGroup.Item key={color}>
{'Color:'} {color} {' Value:'} {balance}
</ListGroup.Item>
)
});
}
return (
<Container>
<h3>Address {addr !== null && <span>({addr.messages.length} Messages)</span>}</h3>
<h3>Address {addr !== null && <span>({addr.output_ids.length} Ouputs)</span>}</h3>
<p>
{id} {' '}
</p>
......@@ -65,15 +144,22 @@ export class ExplorerAddressQueryResult extends React.Component<Props, any> {
addr !== null ?
<React.Fragment>
{
addr.messages !== null && addr.messages.length === 100 &&
addr.output_ids !== null && addr.output_ids.length === 100 &&
<Alert variant={"warning"}>
Max. 100 messages are shown.
Max. 100 outputs are shown.
</Alert>
}
<Row className={"mb-3"}>
<Col>
<ListGroup>
{"Available balances:"} {get_balances(available_balances)}
</ListGroup>
</Col>
</Row>
<Row className={"mb-3"}>
<Col>
<ListGroup variant={"flush"}>
{msgsEle}
{"Outputs detail:"} {outputs}
</ListGroup>
</Col>
</Row>
......
......@@ -154,7 +154,7 @@ export class ExplorerMessageQueryResult extends React.Component<Props, any> {
<Col>
<ListGroup>
<ListGroup.Item className="text-break">
Trunk Message Id: {' '}
Parent 1 Message ID: {' '}
<Link to={`/explorer/message/${msg.trunk_message_id}`}>
{msg.trunk_message_id}
</Link>
......@@ -164,7 +164,7 @@ export class ExplorerMessageQueryResult extends React.Component<Props, any> {
<Col>
<ListGroup>
<ListGroup.Item className="text-break">
Branch Message Id: {' '}
Parent 2 Message ID: {' '}
<Link to={`/explorer/message/${msg.branch_message_id}`}>
{msg.branch_message_id}
</Link>
......
......@@ -22,8 +22,29 @@ export class Message {
}
class AddressResult {
balance: number;
messages: Array<Message>;
address: string;
output_ids: Array<Output>;
}
class Output {
id: string;
balances: Array<Balance>;
inclusion_state: InclusionState;
consumer_count: number;
solidification_time: number;
}
class Balance {
value: number;
color: string;
}
class InclusionState {
liked: boolean;
rejected: boolean;
finalized: boolean;
conflicting: boolean;
confirmed: boolean;
}
class SearchResult {
......@@ -147,9 +168,6 @@ export class ExplorerStore {
@action
updateAddress = (addr: AddressResult) => {
addr.messages = addr.messages.sort((a, b) => {
return a.solidification_timestamp < b.solidification_timestamp ? 1 : -1;
});
this.addr = addr;
this.query_err = null;
this.query_loading = false;
......
This diff is collapsed.
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment