Start of new parachain module (#4309)

This commit is contained in:
Jaco Greeff 2021-01-03 10:32:16 +01:00 committed by GitHub
parent 940687ee78
commit abb9985782
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 94 additions and 1013 deletions

View File

@ -11,7 +11,8 @@ export default function create (t: TFunction): Route {
Component,
display: {
needsApi: [
['query.parachains.code', 'query.parachainUpgrade.didUpdateVFPs']
// children - parachainInfo.arachainId / parachainUpgrade.didSetValidationCode
['query.paras.parachains']
]
},
group: 'network',

View File

@ -1,24 +1,18 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachain } from '@polkadot/api-derive/types';
import type { ThemeProps } from '@polkadot/react-components/types';
import type { Option } from '@polkadot/types';
import type { HeadData } from '@polkadot/types/interfaces';
import type { BlockNumber, HeadData, ParaId } from '@polkadot/types/interfaces';
import React, { useCallback } from 'react';
import { useHistory } from 'react-router-dom';
import React from 'react';
import styled from 'styled-components';
import { Badge, Button, Icon } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import { formatNumber } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
className?: string;
parachain: DeriveParachain;
id: ParaId;
}
const transformHead = {
@ -33,94 +27,32 @@ const transformHead = {
}
};
function Parachain ({ className = '', parachain: { didUpdate, id, info, pendingSwapId, relayDispatchQueueSize = 0 } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const headHex = useCall<string | null>(api.query.parachains.heads, [id], transformHead);
const history = useHistory();
const transformMark = {
transform: (watermark: Option<BlockNumber>): string | null =>
watermark.isSome
? watermark.unwrap().toHuman()
: null
};
const _onClick = useCallback(
() => history.push(`/parachains/${id.toString()}`),
[history, id]
);
function Parachain ({ className = '', id }: Props): React.ReactElement<Props> {
const { api } = useApi();
const headHex = useCall<string | null>(api.query.paras.heads, [id], transformHead);
const watermark = useCall<string | null>(api.query.hrmp.hrmpWatermarks, [id], transformMark);
return (
<tr
className={className}
onClick={_onClick}
>
<tr className={className}>
<td className='number'>
<h1>{id.toString()}</h1>
</td>
<td className='badges'>
<div>
<Badge
className='did-update'
color={didUpdate ? 'green' : 'gray'}
hover={
didUpdate
? t<string>('Updated in the latest block')
: t<string>('Not updated in the last block')
}
info={<Icon icon='check' />}
/>
<Badge
className='pending-messages'
color={relayDispatchQueueSize ? 'counter' : 'gray'}
hover={t<string>('{{relayDispatchQueueSize}} dispatch messages pending', { replace: { relayDispatchQueueSize } })}
info={formatNumber(relayDispatchQueueSize)}
/>
</div>
</td>
<td />
<td className='all start together headhex'>{headHex}</td>
<td className='number pending-swap-id media--800'>
{pendingSwapId?.toString()}
</td>
<td className='number media--800'>
{info?.scheduling?.toString() || t<string>('<unknown>')}
</td>
<td className='button'>
<Button
icon='arrow-right'
label={t('Info')}
onClick={_onClick}
/>
</td>
<td className='number'>{watermark}</td>
</tr>
);
}
export default React.memo(styled(Parachain)(({ theme }: ThemeProps) => `
& {
cursor: pointer !important;
}
h1 {
margin-top: 0 !important;
}
td.badges > div {
width: 3rem;
display: flex;
align-items: center;
}
td.info > div {
display: flex;
align-items: center;
}
td.pending-swap-id {
&, & * {
color: red !important;
}
}
td.headhex {
font: ${theme.fontMono};
}
.did-update {
margin-bottom: 0;
}
`));

View File

@ -1,7 +1,7 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachain } from '@polkadot/api-derive/types';
import type { ParaId } from '@polkadot/types/interfaces';
import React, { useRef } from 'react';
import styled from 'styled-components';
@ -12,29 +12,27 @@ import { useTranslation } from '../translate';
import Parachain from './Parachain';
interface Props {
parachains?: DeriveParachain[];
ids?: ParaId[];
}
function Parachains ({ parachains }: Props): React.ReactElement<Props> {
function Parachains ({ ids }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const headerRef = useRef([
[t('parachains'), 'start', 2],
[t('heads'), 'start'],
[t('swap to id')],
[t('scheduling')],
[]
[t('watermark')]
]);
return (
<Table
empty={parachains && t<string>('There are no registered parachains')}
empty={ids && t<string>('There are no registered parachains')}
header={headerRef.current}
>
{parachains?.map((parachain): React.ReactNode => (
{ids?.map((id): React.ReactNode => (
<Parachain
key={parachain.id.toString()}
parachain={parachain}
id={id}
key={id.toString()}
/>
))}
</Table>

View File

@ -1,53 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ProposalExt } from './types';
import React, { useMemo } from 'react';
import { AddressMini, AddressSmall } from '@polkadot/react-components';
import { FormatBalance } from '@polkadot/react-query';
import { formatNumber } from '@polkadot/util';
interface Props {
proposal: ProposalExt;
}
function Proposal ({ proposal: { id, proposal: { balance, initialHeadState, name, proposer, validationFunction, validators } } }: Props): React.ReactElement<Props> {
const initialHex = useMemo(
(): string => {
const hex = initialHeadState.toHex();
return `${hex.slice(0, 10)}…${hex.slice(-8)}`;
},
[initialHeadState]
);
const validationHex = useMemo(
(): string => {
const hex = validationFunction.toHex();
return `${hex.slice(0, 10)}…${hex.slice(-8)}`;
},
[validationFunction]
);
return (
<tr>
<td className='number'><h1>{formatNumber(id)}</h1></td>
<td className='start together'>{name.toUtf8()}</td>
<td className='address'><AddressSmall value={proposer} /></td>
<td className='balance'><FormatBalance value={balance} /></td>
<td className='start hash together'>{initialHex}</td>
<td className='start hash together'>{validationHex}</td>
<td className='address all'>{validators.map((validatorId) => (
<AddressMini
key={validatorId.toString()}
value={validatorId}
/>
))}</td>
</tr>
);
}
export default React.memo(Proposal);

View File

@ -1,44 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ProposalExt } from './types';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
import Proposal from './Proposal';
interface Props {
proposals?: ProposalExt[];
}
function Proposals ({ proposals }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const headerRef = useRef([
[t('proposals'), 'start', 2],
[t('proposer'), 'address'],
[t('balance')],
[t('initial state'), 'start'],
[t('validation'), 'start'],
[t('validators'), 'address']
]);
return (
<Table
empty={proposals && t<string>('There are no pending proposals')}
header={headerRef.current}
>
{proposals?.map((proposal): React.ReactNode => (
<Proposal
key={proposal.id.toString()}
proposal={proposal}
/>
))}
</Table>
);
}
export default React.memo(Proposals);

View File

@ -1,145 +0,0 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import BN from 'bn.js';
import React, { useMemo, useRef } from 'react';
import { Button, Dropdown, InputFile, InputNumber, InputWasm, Modal, TxButton } from '@polkadot/react-components';
import { useApi, useFormField, useModal } from '@polkadot/react-hooks';
import { BN_THOUSAND, u8aToHex, u8aToString } from '@polkadot/util';
import { useTranslation } from '../translate';
interface Props {
isDisabled: boolean;
nextFreeId?: BN;
sudoKey?: string;
}
type Scheduling = 'Always' | 'Dynamic';
const schedulingOptions = [{
text: 'Always',
value: 'Always'
}, {
text: 'Dynamic',
value: 'Dynamic'
}];
function Register ({ isDisabled, nextFreeId = BN_THOUSAND, sudoKey }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const { isOpen, onClose, onOpen } = useModal();
const onSendRef = useRef<() => void>();
const isWasmValidRef = useRef(false);
const [id, isIdValid, setId] = useFormField<BN>(
nextFreeId,
(id) => !!id && id.gte(nextFreeId)
);
const [code, isCodeValid, setCode] = useFormField<Uint8Array>(
null,
(code) => !!code && isWasmValidRef.current
);
const [initialHeadState, isInitialHeadStateValid, setInitialHeadState] = useFormField<Uint8Array>(null);
const [scheduling, , setScheduling] = useFormField<Scheduling>('Always');
const info = useMemo(
() => api.registry.createType('ParaInfo', { scheduling }),
[api, scheduling]
);
const extrinsic = useMemo(
(): SubmittableExtrinsic | null => {
try {
// FIXME democracy
return api.tx.sudo.sudo(api.tx.registrar.registerPara(
id,
info,
code ? u8aToHex(code) : null,
initialHeadState ? u8aToString(initialHeadState) : null
));
} catch (error) {
console.log(error);
return null;
}
},
[api, id, info, code, initialHeadState]
);
return (
<>
<Button
icon='plus'
isDisabled={isDisabled || !sudoKey}
label={t<string>('Register a parachain')}
onClick={onOpen}
/>
{isOpen && (
<Modal
header={t<string>('Register a parachain')}
onClose={onClose}
small
>
<Modal.Content>
<InputNumber
defaultValue={nextFreeId.toString()}
help={t<string>('The id number to assign to this parachain.')}
isError={!isIdValid}
label={t<string>('parachain id')}
onChange={setId}
onEnter={onSendRef.current}
onEscape={onClose}
placeholder={t<string>('Positive number greater than or equal to {{nextFreeId}}', {
replace: { nextFreeId: nextFreeId.toString() }
})}
/>
<Dropdown
help={t<string>('The scheduling preference for this parachain.')}
label={t<string>('scheduling')}
onChange={setScheduling}
options={schedulingOptions}
value={scheduling}
/>
<InputWasm
help={t<string>('The compiled runtime WASM for the parachain you wish to register.')}
isError={!!code && code.length > 0 && !isCodeValid}
isValidRef={isWasmValidRef}
label={t<string>('code')}
onChange={setCode}
placeholder={
code && code.length > 0 && !isCodeValid
? t<string>('The code is not recognized as being in valid WASM format')
: null
}
/>
<InputFile
help={t<string>('The initial head state for the parachain.')}
isError={!!initialHeadState && !isInitialHeadStateValid}
label={t<string>('initial head state')}
onChange={setInitialHeadState}
placeholder={
initialHeadState && !isInitialHeadStateValid
? t<string>('The initial head state is invalid.')
: null
}
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={sudoKey}
extrinsic={extrinsic}
isDisabled={!isIdValid || !isCodeValid || !isInitialHeadStateValid}
onClick={onClose}
onSendRef={onSendRef}
/>
</Modal.Actions>
</Modal>
)}
</>
);
}
export default React.memo(Register);

View File

@ -1,7 +1,6 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import BN from 'bn.js';
import React from 'react';
import { CardSummary, SummaryBox } from '@polkadot/react-components';
@ -13,11 +12,10 @@ import { useTranslation } from '../translate';
interface Props {
parachainCount?: number;
proposalCount?: number;
nextFreeId?: BN;
upcomingCount?: number;
}
function Summary ({ nextFreeId, parachainCount, proposalCount }: Props): React.ReactElement<Props> {
function Summary ({ parachainCount, upcomingCount }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
@ -25,7 +23,7 @@ function Summary ({ nextFreeId, parachainCount, proposalCount }: Props): React.R
<SummaryBox>
<section>
<CardSummary label={t<string>('relay')}>
{api.query.parachains
{api.query.paras
? t<string>('yes')
: t<string>('no')
}
@ -35,14 +33,9 @@ function Summary ({ nextFreeId, parachainCount, proposalCount }: Props): React.R
{formatNumber(parachainCount)}
</CardSummary>
)}
{isNumber(proposalCount) && (
<CardSummary label={t<string>('proposals')}>
{formatNumber(proposalCount)}
</CardSummary>
)}
{api.query.parachains && nextFreeId && (
<CardSummary label={t<string>('next id')}>
{formatNumber(nextFreeId)}
{isNumber(upcomingCount) && (
<CardSummary label={t<string>('upcoming')}>
{formatNumber(upcomingCount)}
</CardSummary>
)}
</section>

View File

@ -0,0 +1,44 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ParaId } from '@polkadot/types/interfaces';
import React, { useRef } from 'react';
import styled from 'styled-components';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate';
interface Props {
ids?: ParaId[];
}
function Upcoming ({ ids }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const headerRef = useRef([
[t('upcoming'), 'start']
]);
return (
<Table
empty={ids && t<string>('There are no upcoming parachains')}
header={headerRef.current}
>
{ids?.map((id): React.ReactNode => (
<tr key={id.toString()}>
<td className='number'>
<h1>{id.toString()}</h1>
</td>
</tr>
))}
</Table>
);
}
export default React.memo(styled(Upcoming)`
tbody tr {
cursor: pointer;
}
`);

View File

@ -1,64 +1,36 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachain } from '@polkadot/api-derive/types';
import type { Option } from '@polkadot/types';
import type { ParachainProposal, ParaId } from '@polkadot/types/interfaces';
import type { ProposalExt } from './types';
import type { ParaId } from '@polkadot/types/interfaces';
import BN from 'bn.js';
import React from 'react';
import { Button } from '@polkadot/react-components';
import { useApi, useCall } from '@polkadot/react-hooks';
import Transfer from '../Transfer';
import Parachains from './Parachains';
import Proposals from './Proposals';
import Register from './Register';
import Summary from './Summary';
import Upcoming from './Upcoming';
interface Props {
isMine?: boolean;
sudoKey?: string;
className?: string;
}
const transformProposals = {
transform: (entries: [{ args: [ParaId] }, Option<ParachainProposal>][]): ProposalExt[] => {
return entries
.filter(([, opt]) => opt.isSome)
.map(([{ args: [id] }, optProposal]) => ({ id, proposal: optProposal.unwrap() }));
}
};
function Overview ({ isMine, sudoKey }: Props): React.ReactElement<Props> {
function Overview (): React.ReactElement<Props> {
const { api } = useApi();
const parachains = useCall<DeriveParachain[]>(api.derive.parachains?.overview);
const proposals = useCall<ProposalExt[]>(api.query.proposeParachain?.proposals.entries, undefined, transformProposals);
const nextFreeId = useCall<BN>(api.query.registrar?.nextFreeId);
const paraIds = useCall<ParaId[]>(api.query.paras?.parachains);
const upcomingIds = useCall<ParaId[]>(api.query.paras?.upcomingParas);
return (
<>
<Summary
nextFreeId={nextFreeId}
parachainCount={parachains?.length}
proposalCount={proposals?.length}
parachainCount={paraIds?.length}
upcomingCount={upcomingIds?.length}
/>
<Button.Group>
<Transfer parachains={parachains} />
{api.query.parachains && (
<Register
isDisabled={!isMine}
nextFreeId={nextFreeId}
sudoKey={sudoKey}
/>
)}
</Button.Group>
{api.query.parachains && (
<Parachains parachains={parachains} />
)}
{api.query.proposeParachain && (
<Proposals proposals={proposals} />
{api.query.paras && (
<>
<Parachains ids={paraIds} />
<Upcoming ids={upcomingIds} />
</>
)}
</>
);

View File

@ -1,9 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ParachainProposal, ParaId } from '@polkadot/types/interfaces';
export interface ProposalExt {
id: ParaId;
proposal: ParachainProposal;
}

View File

@ -1,83 +0,0 @@
// Copyright 2017-2020 @polkadot/app-democracy authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@polkadot/api/promise/types';
import type { DeriveParachainInfo } from '@polkadot/api-derive/types';
import React, { useCallback, useMemo, useRef } from 'react';
import { Modal, Static, TxButton } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import ParachainInfo from '../ParachainInfo';
import { useTranslation } from '../translate';
interface Props {
id: string;
onClick?: () => void;
info: DeriveParachainInfo | null;
isOpen: boolean;
onClose: () => void;
onSubmit: () => void;
sudoKey: string;
}
function Deregister ({ id, info, isOpen, onClose, onSubmit, sudoKey }: Props): React.ReactElement<Props> | null {
const { t } = useTranslation();
const { api } = useApi();
const onSendRef = useRef<() => void>();
const extrinsic = useMemo(
(): SubmittableExtrinsic | null => {
try {
// FIXME democracy
return api.tx.sudo.sudo(api.tx.registrar.deregisterPara(id));
} catch (error) {
console.log(error);
return null;
}
},
[api, id]
);
const onStart = useCallback(
(): void => {
onClose();
onSubmit();
},
[onClose, onSubmit]
);
return (
<Modal
header={t<string>('Deregister this parachain')}
onClose={onClose}
open={isOpen}
small
>
<Modal.Content>
<ParachainInfo info={info} />
<br />
<br />
<Static
help={t<string>('The id of the parachain to be deregistered.')}
label={t<string>('parachain id')}
value={id}
/>
</Modal.Content>
<Modal.Actions onCancel={onClose}>
<TxButton
accountId={sudoKey}
extrinsic={extrinsic}
isDisabled={!id}
onClick={onClose}
onSendRef={onSendRef}
onStart={onStart}
/>
</Modal.Actions>
</Modal>
);
}
export default React.memo(Deregister);

View File

@ -1,75 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachainFull } from '@polkadot/api-derive/types';
import type { Bytes, Option } from '@polkadot/types';
import FileSaver from 'file-saver';
import React, { useCallback } from 'react';
import { Card, IconLink, Labelled, Output, Static } from '@polkadot/react-components';
import { useApi } from '@polkadot/react-hooks';
import { useTranslation } from '../translate';
import { parachainName } from '../util';
interface Props {
parachain: DeriveParachainFull;
}
function Details ({ parachain: { heads, id, info } }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const _onDownload = useCallback(
(): void => {
api.query.parachains
.code<Option<Bytes>>(id)
.then((code) => {
if (code.isSome) {
const blob = new Blob([code.unwrap().toU8a().buffer], { type: 'application/wasm' });
FileSaver.saveAs(blob, `${id.toString()}_${parachainName(t, info)}.wasm`);
}
})
.catch(console.error);
},
[api, id, info, t]
);
return (
<Card>
<Static
help={t<string>('The scheduling setting for this parachain.')}
isFull
label={t<string>('scheduling')}
value={info?.scheduling?.toString() || t<string>('Unknown')}
/>
{heads && (
<Output
help={t<string>('Most recent head data')}
isFull
isMonospace
label={t<string>('heads')}
value={heads.toHex()}
withCopy
/>
)}
<Labelled
help={t<string>('The compiled runtime WASM for this parachain.')}
isFull
label={t<string>('code')}
>
<div className='ui--Static ui selection dropdown'>
<IconLink
icon='download'
label={t<string>('Download')}
onClick={_onDownload}
/>
</div>
</Labelled>
</Card>
);
}
export default React.memo(Details);

View File

@ -1,50 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { UpwardMessage } from '@polkadot/types/interfaces';
import React from 'react';
import styled from 'styled-components';
import { Card, Output } from '@polkadot/react-components';
import { useTranslation } from '../translate';
interface Props {
className?: string;
dispatchQueue: UpwardMessage[];
}
function DispatchQueue ({ className = '', dispatchQueue = [] }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<div className={className}>
<h1>{t<string>('relay dispatch queue')}</h1>
{dispatchQueue.length === 0
? t<string>('no messages')
: dispatchQueue.map((data): React.ReactNode => (
<Card
className='queue-message'
key={data.toHex()}
>
<Output
help={t<string>('Message data')}
isFull
isMonospace
label={t<string>('data')}
value={data.toHex()}
withCopy
/>
</Card>
))
}
</div>
);
}
export default React.memo(styled(DispatchQueue)`
.queue-message {
margin-bottom: 2rem;
}
`);

View File

@ -1,143 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachainFull, DeriveParachainInfo } from '@polkadot/api-derive/types';
import React, { useCallback } from 'react';
import { useHistory, useParams } from 'react-router-dom';
import styled from 'styled-components';
import { Button, CardSummary, Columar, Icon, Menu, Popup, Spinner, SummaryBox } from '@polkadot/react-components';
import { useApi, useCall, useModal, useToggle } from '@polkadot/react-hooks';
import { isNull, isUndefined } from '@polkadot/util';
import ParachainInfo from '../ParachainInfo';
import { useTranslation } from '../translate';
import Deregister from './Deregister';
import Details from './Details';
import DispatchQueue from './DispatchQueue';
interface Props {
basePath: string;
className?: string;
isMine?: boolean;
paraInfoRef: React.MutableRefObject<DeriveParachainInfo | null>;
sudoKey?: string;
}
function Parachain ({ basePath, className = '', isMine, paraInfoRef, sudoKey }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const history = useHistory();
const { api } = useApi();
const { id } = useParams<{ id: string }>();
const [isMenuOpen, toggleMenu] = useToggle();
const { isOpen: isDeregisterOpen, onClose: onDeregisterClose, onOpen: onDeregisterOpen } = useModal();
const parachain = useCall<DeriveParachainFull | null>(api.derive.parachains.info, [id || null]);
const onDeregister = useCallback(
() => history.push(basePath),
[basePath, history]
);
if (isUndefined(parachain)) {
return (
<Spinner />
);
}
if (!id || isNull(parachain)) {
return (
<>
<article className='error padded'>
<div>
<Icon icon='ban' />
{t(`No parachain with ${id ? `id ${id.toString()}` : 'this id'} exists`)}
</div>
</article>
</>
);
}
if (!paraInfoRef.current) {
paraInfoRef.current = parachain.info;
}
return (
<div className={className}>
<SummaryBox>
<section>
<ParachainInfo
info={parachain.info}
isBig
>
{(sudoKey && isMine) && (
<Popup
isOpen={isMenuOpen}
key='settings'
onClose={toggleMenu}
trigger={
<Button
className='menu-button'
icon='ellipsis-v'
onClick={toggleMenu}
/>
}
>
<Menu
onClick={toggleMenu}
text
vertical
>
<Menu.Item
onClick={(): void => {
toggleMenu();
onDeregisterOpen();
}}
>
{t<string>('Deregister this parachain')}
</Menu.Item>
</Menu>
</Popup>
)}
</ParachainInfo>
</section>
<section>
<CardSummary label={t<string>('parachain id')}>
{id.toString()}
</CardSummary>
{parachain.pendingSwapId && (
<CardSummary label={t<string>('pending swap id')}>
{parachain.pendingSwapId.toString()}
</CardSummary>
)}
</section>
</SummaryBox>
<Columar>
<Columar.Column>
<Details parachain={parachain} />
</Columar.Column>
<Columar.Column>
<DispatchQueue dispatchQueue={parachain.relayDispatchQueue} />
</Columar.Column>
</Columar>
{(sudoKey && isMine) && (
<Deregister
id={id}
info={parachain.info}
isOpen={isDeregisterOpen}
onClose={onDeregisterClose}
onSubmit={onDeregister}
sudoKey={sudoKey}
/>
)}
</div>
);
}
export default React.memo(styled(Parachain)`
& {
.menu-button {
margin-left: 1.4rem;
}
}
`);

View File

@ -1,45 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachainInfo } from '@polkadot/api-derive/types';
import React from 'react';
import { AvatarItem, Icon } from '@polkadot/react-components';
import { useTranslation } from './translate';
import { parachainName, parachainOwner } from './util';
interface Props {
children?: React.ReactNode;
className?: string;
info: DeriveParachainInfo | null;
isBig?: boolean;
}
function ParachainInfo ({ children, className = '', info, isBig }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
return (
<AvatarItem
className={className}
icon={
info?.icon
? <img src={info.icon} />
: (
<Icon
className='highlight--bg'
icon='link'
/>
)
}
isBig={isBig}
subtitle={parachainOwner(t, info)}
title={parachainName(t, info)}
>
{children}
</AvatarItem>
);
}
export default React.memo(ParachainInfo);

View File

@ -1,171 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { SubmittableExtrinsic } from '@polkadot/api/types';
import type { DeriveParachain } from '@polkadot/api-derive/types';
import BN from 'bn.js';
import React, { useMemo, useState } from 'react';
import styled from 'styled-components';
import { Button, Dropdown, Input, InputAddress, InputBalance, InputNumber, Modal, Toggle, TxButton } from '@polkadot/react-components';
import { useAccounts, useApi, useToggle } from '@polkadot/react-hooks';
import { Available } from '@polkadot/react-query';
import { useTranslation } from './translate';
interface Props {
className?: string;
parachains?: DeriveParachain[];
}
function Transfer ({ className, parachains }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const { allAccounts } = useAccounts();
const [isVisible, toggleVisible] = useToggle();
const [accountId, setAccountId] = useState<string | null>(null);
const [recipientId, setRecipientId] = useState<string | null>(null);
const [amount, setAmount] = useState<BN | undefined>();
const [paraId, setParaId] = useState<string | BN | null>(null);
const [remark, setRemark] = useState('');
const [isParachain, setIsParachain] = useState(true);
const idOptions = useMemo((): { text: string, value: string }[] => {
return (parachains || []).map(({ id }) => ({ text: id.toString(), value: id.toString() }));
}, [parachains]);
const extrinsic = useMemo(
(): SubmittableExtrinsic<'promise'> | null => {
if (amount?.gtn(0)) {
if (api.query.parachainUpgrade) {
return recipientId
? isParachain
? api.tx.tokenDealer.transferTokensToParachainChain(paraId, recipientId, amount)
: api.tx.tokenDealer.transferTokensToRelayChain(recipientId, amount)
: null;
} else {
return api.tx.parachains.transferToParachain(paraId, amount, remark);
}
}
return null;
},
[amount, api, isParachain, paraId, recipientId, remark]
);
const isActionDisabled = !allAccounts.length || (
api.query.parachains
? !parachains?.length
: !api.query.parachainUpgrade
);
return (
<>
<Button
icon='paper-plane'
isDisabled={isActionDisabled}
label={t<string>('Transfer to chain')}
onClick={toggleVisible}
/>
{isVisible && (
<Modal
className={className}
header={t<string>('Transfer to chain')}
size='large'
>
<Modal.Content>
<Modal.Columns>
<Modal.Column>
<InputAddress
help={t<string>('The account you will send funds from.')}
label={t<string>('send from account')}
labelExtra={
<Available
label={t<string>('transferrable')}
params={accountId}
/>
}
onChange={setAccountId}
type='account'
/>
<InputAddress
help={t<string>('The account that will receive the funds')}
isDisabled={!api.query.parachainUpgrade}
label={t<string>('send to address')}
onChange={setRecipientId}
value={
api.query.parachainUpgrade
? recipientId
: accountId
}
/>
{isParachain && (
api.query.parachainUpgrade
? (
<InputNumber
help={t<string>('the parachain that will receive the transfer of tokens.')}
label={t<string>('the parachain to transfer to')}
onChange={setParaId}
/>
)
: (
<Dropdown
defaultValue={idOptions[0].value}
help={t<string>('the parachain that will receive the transfer of tokens.')}
label={t<string>('the parachain to transfer to')}
onChange={setParaId}
options={idOptions}
/>
)
)}
<InputBalance
autoFocus
help={t<string>('the amount that should be transferred. It needs to be less than the available balance')}
isZeroable={false}
label={t<string>('amount')}
onChange={setAmount}
/>
{api.query.parachains && (
<Input
help={t<string>('A remark that is associated with this transfer. This can be any comment that indicates the reason.')}
label={t<string>('transfer remark/comment')}
maxLength={32}
onChange={setRemark}
/>
)}
{api.query.parachainUpgrade && (
<Toggle
className='togglePara'
label={t<string>('send funds to another parachain (not relay)')}
onChange={setIsParachain}
value={isParachain}
/>
)}
</Modal.Column>
<Modal.Column>
<p>{t<string>('Transfer an amount from a specific account into a parachain.')}</p>
<p>{t<string>('Once transferred the amount will become available on the chain for use.')}</p>
</Modal.Column>
</Modal.Columns>
</Modal.Content>
<Modal.Actions onCancel={toggleVisible}>
<TxButton
accountId={accountId}
extrinsic={extrinsic}
label={t<string>('Send')}
onStart={toggleVisible}
/>
</Modal.Actions>
</Modal>
)}
</>
);
}
export default React.memo(styled(Transfer)`
.togglePara {
marin-top: 0.5rem;
text-align: right;
}
`);

View File

@ -1,19 +1,13 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { DeriveParachainInfo } from '@polkadot/api-derive/types';
import React, { useMemo, useRef } from 'react';
import { matchPath, Route, Switch } from 'react-router';
import { useLocation } from 'react-router-dom';
import React, { useMemo } from 'react';
import { Route, Switch } from 'react-router';
import { Tabs } from '@polkadot/react-components';
import { useApi, useSudo } from '@polkadot/react-hooks';
import Overview from './Overview';
import Parachain from './Parachain';
import { useTranslation } from './translate';
import { parachainName } from './util';
interface Props {
basePath: string;
@ -21,27 +15,14 @@ interface Props {
function ParachainsApp ({ basePath }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
const sudoState = useSudo();
const paraInfoRef = useRef<DeriveParachainInfo | null>(null);
const location = useLocation();
const match = matchPath<{ id: string }>(location.pathname, { path: `${basePath}/:id` });
const items = useMemo(() => [
{
isRoot: true,
name: 'overview',
text: t<string>('Parachains overview')
},
...(
match && match.params.id
? [{
name: match.params.id,
text: `${match.params.id}: ${parachainName(t, paraInfoRef.current)}`
}]
: []
)
], [match, t]);
}
], [t]);
return (
<main>
@ -53,17 +34,8 @@ function ParachainsApp ({ basePath }: Props): React.ReactElement<Props> {
/>
</header>
<Switch>
{api.query.parachains && (
<Route path={`${basePath}/:id`}>
<Parachain
basePath={basePath}
paraInfoRef={paraInfoRef}
{...sudoState}
/>
</Route>
)}
<Route>
<Overview {...sudoState} />
<Overview />
</Route>
</Switch>
</main>

View File

@ -1,13 +0,0 @@
// Copyright 2017-2020 @polkadot/app-parachains authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { TFunction } from 'i18next';
import type { DeriveParachainInfo } from '@polkadot/api-derive/types';
export function parachainName (t: TFunction, info: DeriveParachainInfo | null): string {
return info?.name || t<string>('Unknown Chain');
}
export function parachainOwner (t: TFunction, info: DeriveParachainInfo | null): string {
return info?.owner || t<string>('Unknown Owner');
}