Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

UI: actor overview #123

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 60 additions & 10 deletions web/api/webrpc/actor_summary.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ import (
"golang.org/x/xerrors"

"github.com/filecoin-project/go-address"
"github.com/filecoin-project/go-state-types/big"

"github.com/filecoin-project/curio/deps/config"
"github.com/filecoin-project/curio/lib/curiochain"

"github.com/filecoin-project/lotus/blockstore"
Expand All @@ -28,6 +30,13 @@ type ActorSummary struct {
Win1, Win7, Win30 int64

Deadlines []ActorDeadline

Wallets map[string][]WalletInfo
}
type WalletInfo struct {
Type string
Address string
Balance string
}

type ActorDeadline struct {
Expand All @@ -39,18 +48,17 @@ type ActorDeadline struct {
}

type minimalActorInfo struct {
Addresses []struct {
MinerAddresses []string
}
Addresses []config.CurioAddresses
}

func (a *WebRPC) ActorSummary(ctx context.Context) ([]ActorSummary, error) {
func (a *WebRPC) ActorSummary(ctx context.Context, expanded bool) ([]ActorSummary, error) {
stor := store.ActorStore(ctx, blockstore.NewReadCachedBlockstore(blockstore.NewAPIBlockstore(a.deps.Chain), curiochain.ChainBlockCache))

var actorInfos []ActorSummary

confNameToAddr := map[address.Address][]string{}

minerWallets := map[address.Address]map[string][]address.Address{}
err := forEachConfig(a, func(name string, info minimalActorInfo) error {
for _, aset := range info.Addresses {
for _, addr := range aset.MinerAddresses {
Expand All @@ -59,6 +67,22 @@ func (a *WebRPC) ActorSummary(ctx context.Context) ([]ActorSummary, error) {
return xerrors.Errorf("parsing address: %w", err)
}
confNameToAddr[a] = append(confNameToAddr[a], name)
if expanded {
minerWallets[a] = map[string][]address.Address{}
for name, aset := range map[string][]string{
"Commit": aset.PreCommitControl,
"Control": aset.CommitControl,
"Terminate": aset.TerminateControl,
} {
for _, addr := range aset {
a, err := address.NewFromString(addr)
if err != nil {
return xerrors.Errorf("parsing address: %w", err)
}
minerWallets[a][name] = append(minerWallets[a][name], a)
}
}
}
}
}
return nil
Expand All @@ -71,6 +95,7 @@ func (a *WebRPC) ActorSummary(ctx context.Context) ([]ActorSummary, error) {
if err != nil {
return nil, xerrors.Errorf("getting sp wins: %w", err)
}
balanceCache := map[address.Address]big.Int{}

for addr, cnames := range confNameToAddr {
p, err := a.deps.Chain.StateMinerPower(ctx, addr, types.EmptyTSK)
Expand Down Expand Up @@ -103,14 +128,17 @@ func (a *WebRPC) ActorSummary(ctx context.Context) ([]ActorSummary, error) {
return nil, xerrors.Errorf("getting miner info: %w", err)
}

wbal, err := a.deps.Chain.WalletBalance(ctx, mi.Worker)
if err != nil {
return nil, xerrors.Errorf("getting worker balance: %w", err)
wbal, ok := balanceCache[mi.Worker]
if !ok {
wbal, err = a.deps.Chain.WalletBalance(ctx, mi.Worker)
if err != nil {
return nil, xerrors.Errorf("getting worker balance: %w", err)
}
balanceCache[mi.Worker] = wbal
}

sort.Strings(cnames)

actorInfos = append(actorInfos, ActorSummary{
as := ActorSummary{
Address: addr.String(),
CLayers: cnames,
QualityAdjustedPower: types.DeciStr(p.MinerPower.QualityAdjPower),
Expand All @@ -122,7 +150,29 @@ func (a *WebRPC) ActorSummary(ctx context.Context) ([]ActorSummary, error) {
Win1: wins[addr].Win1,
Win7: wins[addr].Win7,
Win30: wins[addr].Win30,
})
}
if expanded {

as.Wallets = map[string][]WalletInfo{}
for name, addrs := range minerWallets[addr] {
for _, addr := range addrs {
wb, ok := balanceCache[addr]
if !ok {
wb, err = a.deps.Chain.WalletBalance(ctx, addr)
if err != nil {
return nil, xerrors.Errorf("getting wallet balance: %w", err)
}
balanceCache[addr] = wb
}
as.Wallets[name] = append(as.Wallets[name], WalletInfo{
Type: name,
Address: addr.String(),
Balance: types.FIL(wb).String(),
})
}
}
}
actorInfos = append(actorInfos, as)
}

sort.Slice(actorInfos, func(i, j int) bool {
Expand Down
17 changes: 14 additions & 3 deletions web/static/actor-summary.mjs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
import RPCCall from '/lib/jsonrpc.mjs';
import '/lib/clipboard-copy.mjs';

class Expirations extends LitElement {
static properties = {
Expand Down Expand Up @@ -184,6 +185,11 @@ class ActorSummary extends LitElement {
.deadline-faulty {
background-color: red;
}

.address-container {
display: flex;
align-items: center;
}
`;

constructor() {
Expand All @@ -193,12 +199,12 @@ class ActorSummary extends LitElement {
}

async loadData() {
this.data = await RPCCall('ActorSummary');
this.data = await RPCCall('ActorSummary', false);
this.requestUpdate();

// Poll for updates
setInterval(async () => {
this.data = await RPCCall('ActorSummary');
this.data = await RPCCall('ActorSummary', false);
this.requestUpdate();
}, 30000);
}
Expand Down Expand Up @@ -249,7 +255,12 @@ class ActorSummary extends LitElement {
<tbody>
${this.data.map(entry => html`
<tr>
<td>${entry.Address}</td>
<td>
<div class="address-container">
<a href="/actor/?id=${entry.Address}">${entry.Address}</a>
<clipboard-copy .text=${entry.Address}></clipboard-copy>
</div>
</td>
<td>
${entry.CLayers.map(layer => html`<span>${layer} </span>`)}
</td>
Expand Down
107 changes: 107 additions & 0 deletions web/static/actor/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
<title>Actor Info</title>
<script type="module" src="/ux/curio-ux.mjs"></script>
<script type="module" src="/actor-summary.mjs"></script>
snadrus marked this conversation as resolved.
Show resolved Hide resolved
</head>
<body style="visibility:hidden" data-bs-theme="dark">
<curio-ux>
<actor-info></actor-info>
</curio-ux>
<script>
snadrus marked this conversation as resolved.
Show resolved Hide resolved
import { LitElement, html, css } from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';
import RPCCall from '/lib/jsonrpc.mjs';
customElements.define('actor-info',class Actor extends LitElement {
constructor() {
super();
this.loadData();
}

async loadData() {
this.data = await RPCCall('ActorSummary', true);
snadrus marked this conversation as resolved.
Show resolved Hide resolved
this.requestUpdate();
}
render() {
return html`
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
<link rel="stylesheet" href="/ux/main.css" onload="document.body.style.visibility = 'initial'">
${!this.data ? html`<div>Loading...</div>` : this.data.map(actorInfo => html`
<section class="section">
<div class="row">
<h1>Actor overview</h1>
</div>
/* nicely express actorinfo.
Address:
CLayers:
QualityAdjustedPower:
RawBytePower:
Deadlines:
ActorBalance:
ActorAvailable:
WorkerBalance:
Win1:
Win7:
Win30:
*/
<div class="row">
<div class="col-md-auto">
<div class="info-block dash-tile>
<h3>Actor</h3>
<div>Address: ${actorInfo.Address}</div>
<div>CLayers: ${actorInfo.CLayers}</div>
<div>QualityAdjustedPower: ${actorInfo.QualityAdjustedPower}</div>
<div>RawBytePower: ${actorInfo.RawBytePower}</div>
<div>Deadlines: ${actorInfo.Deadlines}</div>
<div>ActorBalance: ${actorInfo.ActorBalance}</div>
<div>ActorAvailable: ${actorInfo.ActorAvailable}</div>
<div>WorkerBalance: ${actorInfo.WorkerBalance}</div>
<div>Wins 24h: ${actorInfo.Win1}</div>
<div>Win 7 day: ${actorInfo.Win7}</div>
<div>Win 30 day: ${actorInfo.Win30}</div>
</div>
</div>
</div>

<div class="row">
<div class="col-md-auto">
<div class="info-block dash-tile">
<h3>Addresses</h3>
</div>
<table class="table table-dark">
<thead>
<tr>
<th scope="col">Type</th>
<th scope="col">Address</th>
<th scope="col">Balance</th>
</tr>
</thead>
<tbody>
${Object.values(this.data.Wallets).map((wAry) => wAry.map(obj => html`
<tr>
<td>${obj.Type}</td>
<td>${obj.Address}</td>
<td>${obj.Balance}</td>
</tr>
`))}
</tbody>
</table>
</div>
</div>
<div class="row">
<div class="col-md-auto">
<div class="info-block dash-tile">
<h3>Expirations</h3>
<div style="background: #3f3f3f; height: 200px; width: 90vw;"></div>
<sector-expirations address="${actorInfo.Address}"></sector-expirations>
</div>
</div>
</div>
</section>
`)};
`}
}
);
</script>
</body>
</html>
3 changes: 3 additions & 0 deletions web/static/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@
<script type="module" src="pipeline-porep.mjs"></script>
<script type="module" src="actor-summary.mjs"></script>
<script type="module" src="/ux/curio-ux.mjs"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossorigin="anonymous" />
<style>
.logo {
display: inline-block;
Expand Down
57 changes: 57 additions & 0 deletions web/static/lib/clipboard-copy.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import {LitElement, html, css} from 'https://cdn.jsdelivr.net/gh/lit/dist@3/all/lit-all.min.js';

class ClipboardCopy extends LitElement {
static properties = {
text: { type: String },
};

static styles = css`
:host {
display: inline-block;
}
.copy-icon {
cursor: pointer;
margin-left: 0.5em;
color: #6c757d;
transition: color 0.3s ease;
}
.copy-icon:hover {
color: #fff;
}
.copied {
color: #28a745;
}
`;

constructor() {
super();
this.text = '';
this.isCopied = false;
}

render() {
return html`
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"
integrity="sha512-iecdLmaskl7CVkqkXNQ/ZH/XLlvWZOJyj7Yy7tcenmpD1ypASozpmT/E0iPtmFIB46ZmdtAc9eNBvH0H/ZpiBw=="
crossOrigin="anonymous"/>
<span class="copy-icon ${this.isCopied ? 'copied' : ''}" @click=${this._copyToClipboard} title="Copy to clipboard">
<i class="far fa-clipboard"></i>
</span>
`;
}

_copyToClipboard() {
navigator.clipboard.writeText(this.text).then(() => {
console.log('Copied to clipboard:', this.text);
this.isCopied = true;
this.requestUpdate();
setTimeout(() => {
this.isCopied = false;
this.requestUpdate();
}, 2000);
}).catch(err => {
console.error('Failed to copy:', err);
});
}
}
customElements.define('clipboard-copy', ClipboardCopy);