Display ReceivedHeartbeats status for staking accounts (#1476)
* Display ReceivedHeartbeats status for staking accounts * Fix * linting error * complete merge * fix changed event sig * fix recentlyOnline check
This commit is contained in:
parent
d0d9397612
commit
1f51e600d1
@ -3,17 +3,16 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { RecentlyOfflineMap } from '../../types';
|
||||
import { AccountId, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
|
||||
import { DerivedBalances, DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { DerivedBalances, DerivedStaking } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { AccountId, BlockNumber, Exposure, StakingLedger, ValidatorPrefs } from '@polkadot/types/interfaces';
|
||||
import { KeyringSectionOption } from '@polkadot/ui-keyring/options/types';
|
||||
|
||||
import { Popup } from 'semantic-ui-react';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, RecentlyOffline, TxButton } from '@polkadot/react-components';
|
||||
import { AddressCard, AddressInfo, AddressMini, AddressRow, Button, Menu, OnlineStatus, TxButton } from '@polkadot/react-components';
|
||||
import { withCalls, withMulti } from '@polkadot/react-api';
|
||||
|
||||
import BondExtra from './BondExtra';
|
||||
@ -26,12 +25,14 @@ import Unbond from './Unbond';
|
||||
import Validate from './Validate';
|
||||
import { u8aToHex, u8aConcat } from '@polkadot/util';
|
||||
|
||||
import { updateOnlineStatus } from '../../util';
|
||||
|
||||
type Props = ApiProps & I18nProps & {
|
||||
accountId: string;
|
||||
allStashes?: string[];
|
||||
balances_all?: DerivedBalances;
|
||||
className?: string;
|
||||
recentlyOffline: RecentlyOfflineMap;
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
staking_info?: DerivedStaking;
|
||||
stashOptions: KeyringSectionOption[];
|
||||
};
|
||||
@ -51,6 +52,7 @@ interface State {
|
||||
isUnbondOpen: boolean;
|
||||
isValidateOpen: boolean;
|
||||
nominees?: string[];
|
||||
onlineStatus: DerivedStakingOnlineStatus;
|
||||
sessionIds: string[];
|
||||
stakers?: Exposure;
|
||||
stakingLedger?: StakingLedger;
|
||||
@ -79,16 +81,17 @@ class Account extends React.PureComponent<Props, State> {
|
||||
isStashValidating: false,
|
||||
isUnbondOpen: false,
|
||||
isValidateOpen: false,
|
||||
onlineStatus: {},
|
||||
sessionIds: [],
|
||||
stashId: null
|
||||
};
|
||||
|
||||
public static getDerivedStateFromProps ({ allStashes, staking_info }: Props): Pick<State, never> | null {
|
||||
public static getDerivedStateFromProps ({ allStashes, recentlyOnline, staking_info }: Props): Pick<State, never> | null {
|
||||
if (!staking_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { controllerId, nextSessionIds, nominators, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
|
||||
const { controllerId, nextSessionIds, nominators, online, offline, rewardDestination, sessionIds, stakers, stakingLedger, stashId, validatorPrefs } = staking_info;
|
||||
const isStashNominating = nominators && nominators.length !== 0;
|
||||
const _stashId = toIdString(stashId);
|
||||
const isStashValidating = !!allStashes && !!_stashId && allStashes.includes(_stashId);
|
||||
@ -104,6 +107,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
isStashNominating,
|
||||
isStashValidating,
|
||||
nominees: nominators && nominators.map(toIdString),
|
||||
onlineStatus: updateOnlineStatus(recentlyOnline)(sessionIds || null, { online, offline }),
|
||||
sessionIds: (
|
||||
nextSessionIds.length
|
||||
? nextSessionIds
|
||||
@ -131,7 +135,7 @@ class Account extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<AddressCard
|
||||
buttons={this.renderButtons()}
|
||||
iconInfo={this.renderOffline(stashId)}
|
||||
iconInfo={this.renderOnlineStatus()}
|
||||
label={t('stash')}
|
||||
type='account'
|
||||
value={stashId}
|
||||
@ -229,7 +233,6 @@ class Account extends React.PureComponent<Props, State> {
|
||||
{nominees.map((nomineeId, index): React.ReactNode => (
|
||||
<AddressMini
|
||||
key={index}
|
||||
iconInfo={this.renderOffline(nomineeId)}
|
||||
value={nomineeId}
|
||||
withBalance={false}
|
||||
withBonded
|
||||
@ -239,13 +242,17 @@ class Account extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderOffline (address: AccountId | string): React.ReactNode {
|
||||
const { recentlyOffline } = this.props;
|
||||
private renderOnlineStatus (): React.ReactNode {
|
||||
const { onlineStatus, controllerId } = this.state;
|
||||
|
||||
if (!controllerId || !onlineStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<RecentlyOffline
|
||||
accountId={address}
|
||||
offline={recentlyOffline[address.toString()]}
|
||||
<OnlineStatus
|
||||
accountId={controllerId}
|
||||
value={onlineStatus}
|
||||
tooltip
|
||||
/>
|
||||
);
|
||||
@ -262,7 +269,6 @@ class Account extends React.PureComponent<Props, State> {
|
||||
return (
|
||||
<div className='staking--Account-detail actions'>
|
||||
<AddressRow
|
||||
iconInfo={this.renderOffline(controllerId)}
|
||||
label={t('controller')}
|
||||
value={controllerId}
|
||||
withAddressOrName
|
||||
|
||||
@ -32,7 +32,7 @@ class Accounts extends React.PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { allStashes, className, recentlyOffline, t } = this.props;
|
||||
const { allStashes, className, recentlyOnline, t } = this.props;
|
||||
const { isNewStakeOpen } = this.state;
|
||||
const stashOptions = this.getStashOptions();
|
||||
const myStashes = this.getMyStashes();
|
||||
@ -64,7 +64,7 @@ class Accounts extends React.PureComponent<Props, State> {
|
||||
allStashes={allStashes}
|
||||
accountId={address}
|
||||
key={index}
|
||||
recentlyOffline={recentlyOffline}
|
||||
recentlyOnline={recentlyOnline}
|
||||
stashOptions={stashOptions}
|
||||
/>
|
||||
))}
|
||||
@ -112,14 +112,14 @@ class Accounts extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private toggleNewStake = (): void => {
|
||||
this.setState(({ isNewStakeOpen }): State => ({
|
||||
this.setState(({ isNewStakeOpen }): Pick<State, never> => ({
|
||||
isNewStakeOpen: !isNewStakeOpen
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default withMulti(
|
||||
styled(Accounts)`
|
||||
styled(Accounts as React.ComponentClass<Props, State>)`
|
||||
.ui--CardGrid-buttons {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
@ -3,19 +3,20 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, Balance, Exposure } from '@polkadot/types/interfaces';
|
||||
import { DerivedBalancesMap, DerivedStaking } from '@polkadot/api-derive/types';
|
||||
import { AccountId, Balance, BlockNumber, Exposure } from '@polkadot/types/interfaces';
|
||||
import { DerivedBalancesMap, DerivedStaking, DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ValidatorFilter, RecentlyOfflineMap } from '../types';
|
||||
import { ValidatorFilter } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { withCalls, withMulti } from '@polkadot/react-api/with';
|
||||
import { AddressCard, AddressMini, RecentlyOffline } from '@polkadot/react-components';
|
||||
import { AddressCard, AddressMini, OnlineStatus } from '@polkadot/react-components';
|
||||
import { classes } from '@polkadot/react-components/util';
|
||||
import keyring from '@polkadot/ui-keyring';
|
||||
import { formatBalance } from '@polkadot/util';
|
||||
import { updateOnlineStatus } from '../util';
|
||||
|
||||
import translate from '../translate';
|
||||
|
||||
@ -24,15 +25,16 @@ interface Props extends ApiProps, I18nProps {
|
||||
balances: DerivedBalancesMap;
|
||||
className?: string;
|
||||
defaultName: string;
|
||||
filter: ValidatorFilter;
|
||||
lastAuthor: string;
|
||||
lastBlock: string;
|
||||
recentlyOffline: RecentlyOfflineMap;
|
||||
filter: ValidatorFilter;
|
||||
recentlyOnline?: Record<string, BlockNumber>;
|
||||
staking_info?: DerivedStaking;
|
||||
}
|
||||
|
||||
interface State {
|
||||
controllerId: string | null;
|
||||
onlineStatus: DerivedStakingOnlineStatus;
|
||||
stashActive: string | null;
|
||||
stashTotal: string | null;
|
||||
sessionId: string | null;
|
||||
@ -49,6 +51,7 @@ class Address extends React.PureComponent<Props, State> {
|
||||
|
||||
this.state = {
|
||||
controllerId: null,
|
||||
onlineStatus: {},
|
||||
sessionId: null,
|
||||
stashActive: null,
|
||||
stashId: null,
|
||||
@ -57,15 +60,16 @@ class Address extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps ({ staking_info }: Props, prevState: State): Pick<State, never> | null {
|
||||
public static getDerivedStateFromProps ({ recentlyOnline = {}, staking_info }: Props, prevState: State): Pick<State, never> | null {
|
||||
if (!staking_info) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const { controllerId, nextSessionId, stakers, stashId, stakingLedger } = staking_info;
|
||||
const { controllerId, nextSessionId, online, offline, stakers, stakingLedger, stashId } = staking_info;
|
||||
|
||||
return {
|
||||
controllerId: controllerId && controllerId.toString(),
|
||||
onlineStatus: updateOnlineStatus(recentlyOnline)(staking_info.sessionIds || null, { offline, online }),
|
||||
sessionId: nextSessionId && nextSessionId.toString(),
|
||||
stashActive: stakingLedger
|
||||
? formatBalance(stakingLedger.active)
|
||||
@ -98,7 +102,7 @@ class Address extends React.PureComponent<Props, State> {
|
||||
buttons={this.renderKeys()}
|
||||
className={className}
|
||||
defaultName={defaultName}
|
||||
iconInfo={this.renderOffline()}
|
||||
iconInfo={this.renderOnlineStatus()}
|
||||
key={stashId || controllerId || undefined}
|
||||
value={stashId || address}
|
||||
withBalance={{ bonded }}
|
||||
@ -165,10 +169,9 @@ class Address extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private hasWarnings (): boolean {
|
||||
const { recentlyOffline } = this.props;
|
||||
const { stashId } = this.state;
|
||||
const { stashId, onlineStatus } = this.state;
|
||||
|
||||
if (!stashId || !recentlyOffline[stashId]) {
|
||||
if (!stashId || !onlineStatus.offline || !onlineStatus.offline.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -204,20 +207,16 @@ class Address extends React.PureComponent<Props, State> {
|
||||
);
|
||||
}
|
||||
|
||||
private renderOffline (): React.ReactNode {
|
||||
const { recentlyOffline } = this.props;
|
||||
const { stashId } = this.state;
|
||||
|
||||
if (!stashId || !recentlyOffline[stashId]) {
|
||||
private renderOnlineStatus (): React.ReactNode {
|
||||
const { controllerId, onlineStatus } = this.state;
|
||||
if (!controllerId || !onlineStatus) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const offline = recentlyOffline[stashId];
|
||||
|
||||
return (
|
||||
<RecentlyOffline
|
||||
accountId={stashId}
|
||||
offline={offline}
|
||||
<OnlineStatus
|
||||
accountId={controllerId}
|
||||
value={onlineStatus}
|
||||
tooltip
|
||||
/>
|
||||
);
|
||||
|
||||
@ -2,9 +2,10 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { DerivedBalancesMap } from '@polkadot/api-derive/types';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { ValidatorFilter, RecentlyOfflineMap } from '../types';
|
||||
import { ValidatorFilter } from '../types';
|
||||
|
||||
import React from 'react';
|
||||
import { Columar, Column, Dropdown, FilterOverlay } from '@polkadot/react-components';
|
||||
@ -18,7 +19,7 @@ interface Props extends I18nProps {
|
||||
lastAuthor?: string;
|
||||
lastBlock: string;
|
||||
next: string[];
|
||||
recentlyOffline: RecentlyOfflineMap;
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -79,7 +80,7 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
private renderColumn (addresses: string[], defaultName: string): React.ReactNode {
|
||||
const { balances, lastAuthor, lastBlock, recentlyOffline } = this.props;
|
||||
const { balances, lastAuthor, lastBlock, recentlyOnline } = this.props;
|
||||
const { filter } = this.state;
|
||||
|
||||
return addresses.map((address): React.ReactNode => (
|
||||
@ -91,7 +92,7 @@ class CurrentList extends React.PureComponent<Props, State> {
|
||||
filter={filter}
|
||||
lastAuthor={lastAuthor}
|
||||
lastBlock={lastBlock}
|
||||
recentlyOffline={recentlyOffline}
|
||||
recentlyOnline={recentlyOnline}
|
||||
/>
|
||||
));
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ interface Props extends ApiProps, BareProps, ComponentProps {
|
||||
|
||||
class Overview extends React.PureComponent<Props> {
|
||||
public render (): React.ReactNode {
|
||||
const { chain_subscribeNewHeads, allControllers, allStashes, recentlyOffline, currentValidatorsControllersV1OrStashesV2, isSubstrateV2 } = this.props;
|
||||
const { chain_subscribeNewHeads, allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, isSubstrateV2, recentlyOnline } = this.props;
|
||||
let nextSorted: string[];
|
||||
|
||||
if (isSubstrateV2) {
|
||||
@ -59,7 +59,7 @@ class Overview extends React.PureComponent<Props> {
|
||||
lastBlock={lastBlock}
|
||||
lastAuthor={lastAuthor}
|
||||
next={nextSorted}
|
||||
recentlyOffline={recentlyOffline}
|
||||
recentlyOnline={recentlyOnline}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
|
||||
@ -3,15 +3,16 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { AccountId, AuthorityId, BlockNumber, EventRecord } from '@polkadot/types/interfaces';
|
||||
import { AppProps, I18nProps } from '@polkadot/react-components/types';
|
||||
import { ApiProps } from '@polkadot/react-api/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
import { ComponentProps, RecentlyOffline, RecentlyOfflineMap } from './types';
|
||||
import { ComponentProps } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import { Route, Switch } from 'react-router';
|
||||
import { Option } from '@polkadot/types';
|
||||
import { createType, Option } from '@polkadot/types';
|
||||
import { HelpOverlay } from '@polkadot/react-components';
|
||||
import Tabs, { TabItem } from '@polkadot/react-components/Tabs';
|
||||
import { withCalls, withMulti, withObservable } from '@polkadot/react-api';
|
||||
@ -27,15 +28,16 @@ import translate from './translate';
|
||||
type Props = AppProps & ApiProps & I18nProps & {
|
||||
allAccounts?: SubjectInfo;
|
||||
allStashesAndControllers?: [AccountId[], Option<AccountId>[]];
|
||||
chain_bestNumber?: BlockNumber;
|
||||
currentValidatorsControllersV1OrStashesV2?: AccountId[];
|
||||
staking_recentlyOffline?: RecentlyOffline;
|
||||
recentlyOnline?: AuthorityId[];
|
||||
};
|
||||
|
||||
interface State {
|
||||
allControllers: string[];
|
||||
allStashes: string[];
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
recentlyOffline: RecentlyOfflineMap;
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
tabs: TabItem[];
|
||||
}
|
||||
|
||||
@ -51,7 +53,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
allControllers: [],
|
||||
allStashes: [],
|
||||
currentValidatorsControllersV1OrStashesV2: [],
|
||||
recentlyOffline: {},
|
||||
recentlyOnline: {},
|
||||
tabs: [
|
||||
{
|
||||
isRoot: true,
|
||||
@ -66,30 +68,29 @@ class App extends React.PureComponent<Props, State> {
|
||||
};
|
||||
}
|
||||
|
||||
public static getDerivedStateFromProps ({ allStashesAndControllers = [[], []], currentValidatorsControllersV1OrStashesV2 = [], staking_recentlyOffline = [] }: Props): Pick<State, never> {
|
||||
public static getDerivedStateFromProps (props: Props, state: State): Pick<State, never> {
|
||||
const { allStashesAndControllers = [[], []], chain_bestNumber, currentValidatorsControllersV1OrStashesV2 = [] } = props;
|
||||
|
||||
const recentlyOnline: Record<string, BlockNumber> = {
|
||||
...(state.recentlyOnline || {}),
|
||||
...(props.recentlyOnline || []).reduce(
|
||||
(result: Record<string, BlockNumber>, authorityId): Record<string, BlockNumber> => ({
|
||||
...result,
|
||||
[authorityId.toString()]: chain_bestNumber || createType('BlockNumber', new BN(0))
|
||||
}),
|
||||
{}
|
||||
)
|
||||
};
|
||||
|
||||
return {
|
||||
allControllers: allStashesAndControllers[1].filter((optId): boolean => optId.isSome).map((accountId): string =>
|
||||
accountId.unwrap().toString()
|
||||
),
|
||||
allStashes: allStashesAndControllers[0].map((accountId): string => accountId.toString()),
|
||||
allStashes: allStashesAndControllers[0].filter((): boolean => true).map((accountId): string => accountId.toString()),
|
||||
currentValidatorsControllersV1OrStashesV2: currentValidatorsControllersV1OrStashesV2.map((authorityId): string =>
|
||||
authorityId.toString()
|
||||
),
|
||||
recentlyOffline: staking_recentlyOffline.reduce(
|
||||
(result, [accountId, blockNumber, count]): RecentlyOfflineMap => {
|
||||
const account = accountId.toString();
|
||||
|
||||
if (!result[account]) {
|
||||
result[account] = [];
|
||||
}
|
||||
|
||||
result[account].push({
|
||||
blockNumber,
|
||||
count
|
||||
});
|
||||
|
||||
return result;
|
||||
}, {} as unknown as RecentlyOfflineMap)
|
||||
recentlyOnline
|
||||
};
|
||||
}
|
||||
|
||||
@ -120,7 +121,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
|
||||
private renderComponent (Component: React.ComponentType<ComponentProps>): () => React.ReactNode {
|
||||
return (): React.ReactNode => {
|
||||
const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOffline } = this.state;
|
||||
const { allControllers, allStashes, currentValidatorsControllersV1OrStashesV2, recentlyOnline } = this.state;
|
||||
const { allAccounts } = this.props;
|
||||
|
||||
if (!allAccounts) {
|
||||
@ -133,7 +134,7 @@ class App extends React.PureComponent<Props, State> {
|
||||
allControllers={allControllers}
|
||||
allStashes={allStashes}
|
||||
currentValidatorsControllersV1OrStashesV2={currentValidatorsControllersV1OrStashesV2}
|
||||
recentlyOffline={recentlyOffline}
|
||||
recentlyOnline={recentlyOnline}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@ -144,9 +145,23 @@ export default withMulti(
|
||||
App,
|
||||
translate,
|
||||
withCalls<Props>(
|
||||
'derive.chain.bestNumber',
|
||||
['derive.staking.controllers', { propName: 'allStashesAndControllers' }],
|
||||
['query.session.validators', { propName: 'currentValidatorsControllersV1OrStashesV2' }],
|
||||
'query.staking.recentlyOffline'
|
||||
[
|
||||
'query.system.events',
|
||||
{
|
||||
propName: 'recentlyOnline',
|
||||
transform: (value?: EventRecord[]): AuthorityId[] =>
|
||||
(value || [])
|
||||
.filter(({ event: { method, section } }): boolean =>
|
||||
section === 'imOnline' && method === 'HeartbeatReceived'
|
||||
)
|
||||
.map(
|
||||
({ event: { data: [authorityId] } }): AuthorityId => authorityId as AuthorityId
|
||||
)
|
||||
}
|
||||
]
|
||||
),
|
||||
withObservable(accountObservable.subject, { propName: 'allAccounts' })
|
||||
);
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { DerivedFees, DerivedBalances } from '@polkadot/api-derive/types';
|
||||
import { SubjectInfo } from '@polkadot/ui-keyring/observable/types';
|
||||
|
||||
@ -15,7 +15,7 @@ export interface ComponentProps {
|
||||
allControllers: string[];
|
||||
allStashes: string[];
|
||||
currentValidatorsControllersV1OrStashesV2: string[];
|
||||
recentlyOffline: RecentlyOfflineMap;
|
||||
recentlyOnline: Record<string, BlockNumber>;
|
||||
}
|
||||
|
||||
export interface CalculateBalanceProps {
|
||||
@ -24,15 +24,6 @@ export interface CalculateBalanceProps {
|
||||
system_accountNonce?: BN;
|
||||
}
|
||||
|
||||
export type RecentlyOffline = [AccountId, BlockNumber, BN][];
|
||||
|
||||
export type RecentlyOfflineMap = Record<string, OfflineStatus[]>;
|
||||
|
||||
export interface OfflineStatus {
|
||||
blockNumber: BlockNumber;
|
||||
count: BN;
|
||||
}
|
||||
|
||||
export type AccountFilter = 'all' | 'controller' | 'session' | 'stash' | 'unbonded';
|
||||
|
||||
export type ValidatorFilter = 'all' | 'hasNominators' | 'noNominators' | 'hasWarnings' | 'noWarnings' | 'iNominated';
|
||||
|
||||
31
packages/app-staking/src/util.ts
Normal file
31
packages/app-staking/src/util.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/* eslint-disable @typescript-eslint/camelcase */
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId, BlockNumber } from '@polkadot/types/interfaces';
|
||||
import { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
|
||||
|
||||
export function updateOnlineStatus (recentlyOnline?: Record<string, BlockNumber>): (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus) => DerivedStakingOnlineStatus {
|
||||
return (sessionIds: AccountId[] | null, onlineStatus: DerivedStakingOnlineStatus): DerivedStakingOnlineStatus => {
|
||||
if (!recentlyOnline || !sessionIds) {
|
||||
return onlineStatus;
|
||||
}
|
||||
|
||||
const sessionId: AccountId | undefined = sessionIds.find((accountId): boolean => Object.keys(recentlyOnline).includes(accountId.toString()));
|
||||
|
||||
return {
|
||||
...onlineStatus,
|
||||
...(
|
||||
sessionId
|
||||
? {
|
||||
online: {
|
||||
blockNumber: recentlyOnline[sessionId.toString()],
|
||||
isOnline: true
|
||||
}
|
||||
}
|
||||
: {}
|
||||
)
|
||||
};
|
||||
};
|
||||
}
|
||||
175
packages/react-components/src/OnlineStatus.tsx
Normal file
175
packages/react-components/src/OnlineStatus.tsx
Normal file
@ -0,0 +1,175 @@
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { DerivedStakingOnlineStatus } from '@polkadot/api-derive/types';
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from './types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Icon from './Icon';
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
import translate from './translate';
|
||||
import { classes } from './util';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
accountId: AccountId | string;
|
||||
tooltip?: boolean;
|
||||
value: DerivedStakingOnlineStatus;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isOpen: boolean;
|
||||
version: 'online' | 'offline' | null;
|
||||
}
|
||||
|
||||
class OnlineStatus extends React.PureComponent<Props, State> {
|
||||
public static getDerivedStateFromProps ({ value = {} }: Props): Pick<State, never> {
|
||||
const { online, offline } = value;
|
||||
|
||||
if (offline) {
|
||||
return { version: 'offline' };
|
||||
}
|
||||
if (online && online.isOnline) {
|
||||
return { version: 'online' };
|
||||
}
|
||||
return { version: null };
|
||||
}
|
||||
|
||||
public state: State = {
|
||||
isOpen: false,
|
||||
version: null
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { accountId, className, value, tooltip = false, t } = this.props;
|
||||
const { isOpen, version } = this.state;
|
||||
|
||||
const key = accountId.toString();
|
||||
const classNames: (string | false | undefined)[] = ['ui--OnlineStatus', isOpen && 'expand', tooltip && 'tooltip', className];
|
||||
let text: string;
|
||||
let contents: React.ReactNode;
|
||||
let offline;
|
||||
let count;
|
||||
let blockNumbers;
|
||||
let blockNumber;
|
||||
|
||||
switch (version) {
|
||||
case 'offline':
|
||||
offline = value.offline || [];
|
||||
count = offline.reduce((total, { count }): BN => total.add(count), new BN(0));
|
||||
blockNumbers = offline.map(({ blockNumber }): string => `#${formatNumber(blockNumber)}`);
|
||||
|
||||
classNames.push('offline');
|
||||
contents = count.toString();
|
||||
text = t('Reported offline {{count}} times, last at {{blockNumber}}', {
|
||||
replace: {
|
||||
count,
|
||||
blockNumber: blockNumbers[blockNumbers.length - 1]
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
case 'online':
|
||||
blockNumber = value.online ? value.online.blockNumber : null;
|
||||
|
||||
classNames.push('online');
|
||||
contents = (
|
||||
<Icon name='check' />
|
||||
);
|
||||
text = blockNumber
|
||||
? t('Reported online at #{{blockNumber}}', {
|
||||
replace: {
|
||||
blockNumber: formatNumber(blockNumber)
|
||||
}
|
||||
})
|
||||
: t('Reported online in the current session');
|
||||
break;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes(...classNames)}
|
||||
{...(!tooltip ? { onClick: this.toggleOpen } : {})}
|
||||
data-for={`online-status-${key}`}
|
||||
data-tip={true}
|
||||
data-tip-disable={!tooltip}
|
||||
>
|
||||
<div className='badge'>
|
||||
{contents}
|
||||
</div>
|
||||
<div className='detail'>
|
||||
{text}
|
||||
</div>
|
||||
<Tooltip
|
||||
trigger={`online-status-${key}`}
|
||||
text={text}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleOpen = (): void => {
|
||||
this.setState(({ isOpen }): Pick<State, never> => ({
|
||||
isOpen: !isOpen
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(styled(OnlineStatus as React.ComponentClass<Props, State>)`
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
|
||||
color: #eee;
|
||||
cursor: help;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
justify-content: center;
|
||||
padding: 0 4px;
|
||||
text-align: center;
|
||||
transition: all ease .2s;
|
||||
width: 22px;
|
||||
|
||||
&.offline {
|
||||
background: red;
|
||||
}
|
||||
|
||||
&.online {
|
||||
background: green;
|
||||
}
|
||||
|
||||
& > * {
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
transition: all ease 0.25;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-weight: bold;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&.expand {
|
||||
width: 300px;
|
||||
|
||||
.badge {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -1,117 +0,0 @@
|
||||
// Copyright 2017-2019 @polkadot/app-staking authors & contributors
|
||||
// This software may be modified and distributed under the terms
|
||||
// of the Apache-2.0 license. See the LICENSE file for details.
|
||||
|
||||
import { AccountId } from '@polkadot/types/interfaces';
|
||||
import { I18nProps } from '@polkadot/react-components/types';
|
||||
import { OfflineStatus } from '@polkadot/app-staking/types';
|
||||
|
||||
import BN from 'bn.js';
|
||||
import React from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { formatNumber } from '@polkadot/util';
|
||||
|
||||
import Tooltip from './Tooltip';
|
||||
|
||||
import translate from './translate';
|
||||
import { classes } from './util';
|
||||
|
||||
interface Props extends I18nProps {
|
||||
accountId: AccountId | string;
|
||||
offline: OfflineStatus[];
|
||||
tooltip?: boolean;
|
||||
}
|
||||
|
||||
interface State {
|
||||
isOpen: boolean;
|
||||
}
|
||||
|
||||
class RecentlyOffline extends React.PureComponent<Props, State> {
|
||||
public state: State = {
|
||||
isOpen: false
|
||||
};
|
||||
|
||||
public render (): React.ReactNode {
|
||||
const { accountId, className, offline, tooltip = false, t } = this.props;
|
||||
const { isOpen } = this.state;
|
||||
|
||||
if (!offline) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const count = offline.reduce((total, { count }): BN => total.add(count), new BN(0));
|
||||
const blockNumbers = offline.map(({ blockNumber }): string => `#${formatNumber(blockNumber)}`);
|
||||
const text = t('Reported offline {{count}} times, last at {{blockNumber}}', {
|
||||
replace: {
|
||||
count,
|
||||
blockNumber: blockNumbers[blockNumbers.length - 1]
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={classes('ui--RecentlyOffline', isOpen && 'expand', tooltip && 'tooltip', className)}
|
||||
{...(!tooltip ? { onClick: this.toggleOpen } : {})}
|
||||
data-for={`offline-${accountId}`}
|
||||
data-tip={true}
|
||||
data-tip-disable={!tooltip}
|
||||
>
|
||||
<div className='badge'>{count.toString()}</div>
|
||||
<div className='detail'>{text}</div>
|
||||
<Tooltip
|
||||
trigger={`offline-${accountId}`}
|
||||
text={text}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
private toggleOpen = (): void => {
|
||||
this.setState(({ isOpen }): State => ({
|
||||
isOpen: !isOpen
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
export default translate(styled(RecentlyOffline)`
|
||||
background: red;
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 3px 3px rgba(0, 0, 0, 0.2);
|
||||
color: #eee;
|
||||
cursor: help;
|
||||
display: flex;
|
||||
font-size: 12px;
|
||||
height: 22px;
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
text-align: center;
|
||||
transition: all ease .2s;
|
||||
width: 22px;
|
||||
|
||||
& > * {
|
||||
line-height: 22px;
|
||||
overflow: hidden;
|
||||
transition: all ease 0.25;
|
||||
}
|
||||
|
||||
.badge {
|
||||
font-weight: bold;
|
||||
width: auto;
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
&.expand {
|
||||
width: 300px;
|
||||
|
||||
.badge {
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.detail {
|
||||
width: auto;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -59,7 +59,7 @@ export { default as Output } from './Output';
|
||||
export { default as Password } from './Password';
|
||||
export { default as Progress } from './Progress';
|
||||
export { default as ProposedAction } from './ProposedAction';
|
||||
export { default as RecentlyOffline } from './RecentlyOffline';
|
||||
export { default as OnlineStatus } from './OnlineStatus';
|
||||
export { default as Row } from './Row';
|
||||
export { default as Static } from './Static';
|
||||
export { default as Status } from './Status';
|
||||
|
||||
Loading…
Reference in New Issue
Block a user