Start of new parachain module (#4309)
This commit is contained in:
parent
940687ee78
commit
abb9985782
@ -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',
|
||||
|
||||
@ -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;
|
||||
}
|
||||
`));
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
@ -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);
|
||||
@ -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);
|
||||
@ -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>
|
||||
|
||||
44
packages/page-parachains/src/Overview/Upcoming.tsx
Normal file
44
packages/page-parachains/src/Overview/Upcoming.tsx
Normal 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;
|
||||
}
|
||||
`);
|
||||
@ -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} />
|
||||
</>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
@ -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;
|
||||
}
|
||||
@ -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);
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
`);
|
||||
@ -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);
|
||||
@ -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;
|
||||
}
|
||||
`);
|
||||
@ -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>
|
||||
|
||||
@ -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');
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user