Relay Chain - Coretime Overview UI Tab (#11016)
* exposing coretime wss endpoint to use on relay chain, adjusted hooks to accept api endpoint as a parameter * adjusting hook calls to new parameters * added Tag for coretime types, renamed occupancyType to coretimeType * added cycle progress and cycle end date to summary, fixed the estimation dates * coretime page draft, added hook for gathering all the coretime information in order to show it on relay chain * lint * bumped version * updated coretime chain summary * updated summary, added timeslice for the first sale start, * hook updates to make sure the coretime data is displayed even if workloads are empty, fallback on coreAssignments * removed unused files * lint * tsc * merging parachainIds with renewalIds * if there is no record in potential renewals, then the chain has renewed already * added config for page-coretime * removed hardcoded regionLength value * improved function to find out type * lint * tsc * Fix versioning * fix comments --------- Co-authored-by: tarikgul <tariksnow37@gmail.com>
This commit is contained in:
parent
9134c0e32b
commit
4df5c9a8d5
@ -351,7 +351,7 @@ export const testParasPaseoCommon: EndpointOption[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
info: 'BridgeHub',
|
||||
info: 'PaseoBridgeHub',
|
||||
isPeopleForIdentity: true,
|
||||
paraId: 1002,
|
||||
providers: {
|
||||
@ -367,7 +367,7 @@ export const testParasPaseoCommon: EndpointOption[] = [
|
||||
}
|
||||
},
|
||||
{
|
||||
info: 'Coretime',
|
||||
info: 'PaseoCoretime',
|
||||
isPeopleForIdentity: true,
|
||||
paraId: 1005,
|
||||
providers: {
|
||||
|
||||
22
packages/apps-routing/src/coretime.ts
Normal file
22
packages/apps-routing/src/coretime.ts
Normal file
@ -0,0 +1,22 @@
|
||||
// Copyright 2017-2024 @polkadot/apps-routing authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Route, TFunction } from './types.js';
|
||||
|
||||
import Component from '@polkadot/app-coretime';
|
||||
|
||||
export default function create (t: TFunction): Route {
|
||||
return {
|
||||
Component,
|
||||
display: {
|
||||
needsApi: [
|
||||
'query.coretimeAssignmentProvider.coreDescriptors'
|
||||
],
|
||||
needsApiInstances: true
|
||||
},
|
||||
group: 'network',
|
||||
icon: 'flask',
|
||||
name: 'coretime',
|
||||
text: t('nav.coretime', 'Coretime (Experimental)', { ns: 'apps-routing' })
|
||||
};
|
||||
}
|
||||
@ -14,6 +14,7 @@ import calendar from './calendar.js';
|
||||
import claims from './claims.js';
|
||||
import collator from './collator.js';
|
||||
import contracts from './contracts.js';
|
||||
import coretime from './coretime.js';
|
||||
import council from './council.js';
|
||||
import democracy from './democracy.js';
|
||||
import explorer from './explorer.js';
|
||||
@ -62,7 +63,9 @@ export default function create (t: TFunction): Routes {
|
||||
// Legacy staking Pre v14 pallet version.
|
||||
stakingLegacy(t),
|
||||
collator(t),
|
||||
// Coretime
|
||||
broker(t),
|
||||
coretime(t),
|
||||
// governance v2
|
||||
referenda(t),
|
||||
membership(t),
|
||||
|
||||
@ -11,12 +11,13 @@
|
||||
{ "path": "../page-alliance/tsconfig.build.json" },
|
||||
{ "path": "../page-ambassador/tsconfig.build.json" },
|
||||
{ "path": "../page-assets/tsconfig.build.json" },
|
||||
{ "path": "../page-broker/tsconfig.build.json" },
|
||||
{ "path": "../page-bounties/tsconfig.build.json" },
|
||||
{ "path": "../page-calendar/tsconfig.build.json" },
|
||||
{ "path": "../page-claims/tsconfig.build.json" },
|
||||
{ "path": "../page-broker/tsconfig.build.json" },
|
||||
{ "path": "../page-collator/tsconfig.build.json" },
|
||||
{ "path": "../page-contracts/tsconfig.build.json" },
|
||||
{ "path": "../page-coretime/tsconfig.build.json" },
|
||||
{ "path": "../page-council/tsconfig.build.json" },
|
||||
{ "path": "../page-democracy/tsconfig.build.json" },
|
||||
{ "path": "../page-explorer/tsconfig.build.json" },
|
||||
|
||||
@ -2,6 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { PalletBrokerConfigRecord } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
@ -14,11 +15,12 @@ import Workload from './Workload.js';
|
||||
interface Props {
|
||||
api: ApiPromise;
|
||||
core: number;
|
||||
config: PalletBrokerConfigRecord,
|
||||
workload?: CoreWorkloadType[],
|
||||
workplan?: CoreWorkplanType[],
|
||||
}
|
||||
|
||||
function CoreTable ({ api, core, workload, workplan }: Props): React.ReactElement<Props> {
|
||||
function CoreTable ({ api, config, core, workload, workplan }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const headerRef = useRef<([React.ReactNode?, string?] | false)[]>([[t('core')]]);
|
||||
const header: [React.ReactNode?, string?, number?, (() => void)?][] = [
|
||||
@ -39,6 +41,7 @@ function CoreTable ({ api, core, workload, workplan }: Props): React.ReactElemen
|
||||
>
|
||||
<Workload
|
||||
api={api}
|
||||
config={config}
|
||||
core={core}
|
||||
key={core}
|
||||
workload={workload}
|
||||
|
||||
@ -6,20 +6,26 @@ import type { CoreInfo } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useBrokerConfig } from '@polkadot/react-hooks';
|
||||
|
||||
import CoreTable from './CoreTable.js';
|
||||
|
||||
interface Props {
|
||||
api: ApiPromise;
|
||||
isApiReady: boolean;
|
||||
data: CoreInfo[];
|
||||
}
|
||||
|
||||
function CoresTable ({ api, data }: Props): React.ReactElement<Props> {
|
||||
function CoresTable ({ api, data, isApiReady }: Props): React.ReactElement<Props> {
|
||||
const config = useBrokerConfig(api, isApiReady);
|
||||
|
||||
return (
|
||||
<>
|
||||
{data?.map((coreData) => {
|
||||
{config && data?.map((coreData) => {
|
||||
return (
|
||||
<CoreTable
|
||||
api={api}
|
||||
config={config}
|
||||
core={coreData?.core}
|
||||
key={coreData?.core}
|
||||
workload={coreData?.workload}
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
import type { statsType } from '../types.js';
|
||||
|
||||
import React from 'react';
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { CardSummary, styled, SummaryBox, UsageBar } from '@polkadot/react-components';
|
||||
import { defaultHighlight } from '@polkadot/react-components/styles';
|
||||
import { useApi, useBrokerSalesInfo, useBrokerStatus } from '@polkadot/react-hooks';
|
||||
import { useApi, useBrokerConfig, useBrokerSalesInfo, useBrokerStatus } from '@polkadot/react-hooks';
|
||||
import { type CoreWorkload } from '@polkadot/react-hooks/types';
|
||||
import { formatBalance } from '@polkadot/util';
|
||||
import { BN, BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import { getStats } from '../utils.js';
|
||||
import { estimateTime, getStats } from '../utils.js';
|
||||
import RegionLength from './Summary/RegionLength.js';
|
||||
import Timeslice from './Summary/Timeslice.js';
|
||||
import TimeslicePeriod from './Summary/TimeslicePeriod.js';
|
||||
@ -41,12 +41,15 @@ interface Props {
|
||||
|
||||
function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { api, apiEndpoint } = useApi();
|
||||
const totalCores = useBrokerStatus('coreCount');
|
||||
const { api, apiEndpoint, isApiReady } = useApi();
|
||||
const uiHighlight = apiEndpoint?.ui.color || defaultHighlight;
|
||||
const { idles, pools, tasks }: statsType = React.useMemo(() => getStats(totalCores, workloadInfos), [totalCores, workloadInfos]);
|
||||
const { idles, pools, tasks }: statsType = React.useMemo(() => getStats(coreCount, workloadInfos), [coreCount, workloadInfos]);
|
||||
|
||||
const salesInfo = useBrokerSalesInfo();
|
||||
const saleInfo = useBrokerSalesInfo(api, isApiReady);
|
||||
const config = useBrokerConfig(api, isApiReady);
|
||||
const status = useBrokerStatus(api, isApiReady);
|
||||
const currentRegionEnd = useMemo(() => saleInfo && config && saleInfo?.regionEnd - config?.regionLength, [saleInfo, config]);
|
||||
const currentRegionStart = useMemo(() => currentRegionEnd && config && currentRegionEnd - config?.regionLength, [currentRegionEnd, config]);
|
||||
|
||||
return (
|
||||
<SummaryBox>
|
||||
@ -63,24 +66,31 @@ function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement {
|
||||
<CardSummary label={t('region (ts)')}>
|
||||
<RegionLength />
|
||||
</CardSummary>
|
||||
<CardSummary label={t('estimated bulk price')}>
|
||||
<div className='ui--balance-value'>
|
||||
{formatBalance(salesInfo?.endPrice) || '-'}
|
||||
</div>
|
||||
</CardSummary>
|
||||
<CardSummary label={t('total cores')}>
|
||||
{coreCount}
|
||||
</CardSummary>
|
||||
<CardSummary label={t('cores sold/offered')}>
|
||||
<div>
|
||||
{salesInfo?.coresSold} / {salesInfo?.coresOffered}
|
||||
{saleInfo?.coresSold} / {saleInfo?.coresOffered}
|
||||
</div>
|
||||
</CardSummary>
|
||||
<CardSummary
|
||||
label={t('cycle progress')}
|
||||
progress={{
|
||||
isBlurred: false,
|
||||
total: new BN(config?.regionLength || 0),
|
||||
value: (config?.regionLength && currentRegionEnd && status && new BN(config?.regionLength - (currentRegionEnd - status?.lastTimeslice))) || BN_ZERO,
|
||||
withTime: false
|
||||
}}
|
||||
/>
|
||||
</StyledDiv>
|
||||
</>
|
||||
|
||||
)}
|
||||
<div style={{ marginLeft: '2rem' }}>
|
||||
<div
|
||||
className='media--1400'
|
||||
style={{ marginLeft: '2rem' }}
|
||||
>
|
||||
<UsageBar
|
||||
data={[
|
||||
{ color: '#FFFFFF', label: 'Idle', value: idles },
|
||||
@ -91,6 +101,32 @@ function Summary ({ coreCount, workloadInfos }: Props): React.ReactElement {
|
||||
|
||||
</div>
|
||||
</StyledSection>
|
||||
<section>
|
||||
{status && currentRegionStart && currentRegionEnd &&
|
||||
(
|
||||
<>
|
||||
<CardSummary
|
||||
className='media--1200'
|
||||
label={t('cycle dates')}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{estimateTime(currentRegionStart, status?.lastTimeslice * 80)}</div>
|
||||
<div style={{ fontSize: '14px' }}>{estimateTime(currentRegionEnd, status?.lastTimeslice * 80)}</div>
|
||||
</div>
|
||||
</CardSummary>
|
||||
<CardSummary
|
||||
className='media--1200'
|
||||
label={t('cycle ts')}
|
||||
>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionStart}</div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionEnd}</div>
|
||||
</div>
|
||||
</CardSummary>
|
||||
</>
|
||||
)
|
||||
}
|
||||
</section>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
@ -1,24 +0,0 @@
|
||||
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BrokerStatus } from '@polkadot/react-query';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function Cores ({ children, className }: Props): React.ReactElement<Props> | null {
|
||||
return (
|
||||
<BrokerStatus
|
||||
className={className}
|
||||
query='coreCount'
|
||||
>
|
||||
{children}
|
||||
</BrokerStatus>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Cores);
|
||||
@ -1,26 +0,0 @@
|
||||
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useBrokerSalesInfo } from '@polkadot/react-hooks';
|
||||
import { formatBalance } from '@polkadot/util';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
children?: React.ReactNode;
|
||||
renewalBump?: string;
|
||||
currentPrice?: string;
|
||||
}
|
||||
|
||||
function RenewalPrice (): React.ReactElement<Props> | null {
|
||||
const salesInfo = useBrokerSalesInfo();
|
||||
|
||||
return (
|
||||
<div className='ui--balance-value'>
|
||||
{formatBalance(salesInfo?.endPrice) || '-'}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(RenewalPrice);
|
||||
@ -3,7 +3,7 @@
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { BrokerStatus } from '@polkadot/react-query';
|
||||
import { useApi, useBrokerStatus } from '@polkadot/react-hooks';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
@ -11,14 +11,14 @@ interface Props {
|
||||
}
|
||||
|
||||
function Timeslice ({ children, className }: Props): React.ReactElement<Props> | null {
|
||||
const { api, isApiReady } = useApi();
|
||||
const info = useBrokerStatus(api, isApiReady);
|
||||
|
||||
return (
|
||||
<BrokerStatus
|
||||
className={className}
|
||||
query='lastTimeslice'
|
||||
>
|
||||
<div className={className}>
|
||||
{info?.lastTimeslice || '-'}
|
||||
{children}
|
||||
</BrokerStatus>
|
||||
);
|
||||
</div>);
|
||||
}
|
||||
|
||||
export default React.memo(Timeslice);
|
||||
|
||||
@ -1,11 +1,20 @@
|
||||
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FlagColor } from '@polkadot/react-components/types';
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { AddressMini, styled } from '@polkadot/react-components';
|
||||
import { AddressMini, styled, Tag } from '@polkadot/react-components';
|
||||
|
||||
import { type InfoRow, Occupancy } from '../types.js';
|
||||
import { CoreTimeTypes, type InfoRow } from '../types.js';
|
||||
|
||||
const colours: Record<string, string> = {
|
||||
[CoreTimeTypes.Reservation]: 'orange',
|
||||
[CoreTimeTypes.Lease]: 'blue',
|
||||
[CoreTimeTypes['Bulk Coretime']]: 'pink',
|
||||
[CoreTimeTypes['On Demand']]: 'green'
|
||||
};
|
||||
|
||||
const StyledTableCol = styled.td<{ hide?: 'mobile' | 'tablet' | 'both' }>`
|
||||
width: 150px;
|
||||
@ -45,7 +54,7 @@ function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement {
|
||||
}
|
||||
|
||||
switch (data.type) {
|
||||
case (Occupancy.Reservation): {
|
||||
case (CoreTimeTypes.Reservation): {
|
||||
return (
|
||||
<>
|
||||
<TableCol
|
||||
@ -56,16 +65,18 @@ function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement {
|
||||
header='Blocks per timeslice'
|
||||
value={data.maskBits}
|
||||
/>
|
||||
<TableCol
|
||||
header='type'
|
||||
value={'Reservation'}
|
||||
/>
|
||||
<td>
|
||||
<Tag
|
||||
color={colours[data.type] as FlagColor}
|
||||
label={Object.values(CoreTimeTypes)[data.type]}
|
||||
/>
|
||||
</td>
|
||||
<td colSpan={4} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
case (Occupancy.Lease): {
|
||||
case (CoreTimeTypes.Lease):
|
||||
return (
|
||||
<>
|
||||
<TableCol
|
||||
@ -90,10 +101,48 @@ function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement {
|
||||
header='Last block'
|
||||
value={data.endBlock}
|
||||
/>
|
||||
<StyledTableCol hide={'mobile'}>
|
||||
<h5 style={{ opacity: '0.6' }}>type</h5>
|
||||
<Tag
|
||||
color={colours[data.type] as FlagColor}
|
||||
label={Object.values(CoreTimeTypes)[data.type]}
|
||||
/>
|
||||
</StyledTableCol>
|
||||
<td colSpan={1} />
|
||||
</>);
|
||||
|
||||
case (CoreTimeTypes['On Demand']): {
|
||||
return (
|
||||
<>
|
||||
<TableCol
|
||||
header='type'
|
||||
value={'Legacy Lease'}
|
||||
header='Task'
|
||||
value={data.task}
|
||||
/>
|
||||
<TableCol
|
||||
header='Blocks per timeslice'
|
||||
value={data.maskBits}
|
||||
/>
|
||||
<TableCol
|
||||
header='Start'
|
||||
hide='both'
|
||||
value={data.start}
|
||||
/>
|
||||
<TableCol
|
||||
header='End'
|
||||
hide='both'
|
||||
value={data.end}
|
||||
/>
|
||||
<TableCol
|
||||
header='Last block'
|
||||
value={data.endBlock}
|
||||
/>
|
||||
<StyledTableCol hide={'mobile'}>
|
||||
<h5 style={{ opacity: '0.6' }}>type</h5>
|
||||
<Tag
|
||||
color={colours[data.type] as FlagColor}
|
||||
label={Object.values(CoreTimeTypes)[data.type]}
|
||||
/>
|
||||
</StyledTableCol>
|
||||
<td colSpan={1} />
|
||||
</>);
|
||||
}
|
||||
@ -123,23 +172,24 @@ function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement {
|
||||
header='Last block'
|
||||
value={data.endBlock}
|
||||
/>
|
||||
<TableCol
|
||||
header='type'
|
||||
value={'Bulk Coretime'}
|
||||
/>
|
||||
<StyledTableCol hide='mobile'>
|
||||
<h5 style={{ opacity: '0.6' }}>{'Owner'}</h5>
|
||||
{data.owner
|
||||
? (
|
||||
<AddressMini
|
||||
isPadded={false}
|
||||
key={data.owner}
|
||||
value={data.owner}
|
||||
/>
|
||||
)
|
||||
|
||||
: <p> </p>}
|
||||
<StyledTableCol hide={'mobile'}>
|
||||
<h5 style={{ opacity: '0.6' }}>type</h5>
|
||||
<Tag
|
||||
color={colours[CoreTimeTypes['Bulk Coretime']] as FlagColor}
|
||||
label={'Bulk Coretime'}
|
||||
/>
|
||||
</StyledTableCol>
|
||||
{data.owner
|
||||
? <StyledTableCol hide='mobile'>
|
||||
<h5 style={{ opacity: '0.6' }}>{'Owner'}</h5>
|
||||
<AddressMini
|
||||
isPadded={false}
|
||||
key={data.owner}
|
||||
value={data.owner}
|
||||
/>
|
||||
|
||||
</StyledTableCol>
|
||||
: <td></td>}
|
||||
</>);
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { RegionInfo } from '@polkadot/react-hooks/types';
|
||||
import type { PalletBrokerConfigRecord, RegionInfo } from '@polkadot/react-hooks/types';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup';
|
||||
import type { CoreWorkloadType, CoreWorkplanType, InfoRow } from '../types.js';
|
||||
@ -21,11 +21,12 @@ interface Props {
|
||||
core: number;
|
||||
workload: CoreWorkloadType[] | undefined
|
||||
workplan?: CoreWorkplanType[] | undefined
|
||||
config: PalletBrokerConfigRecord
|
||||
}
|
||||
|
||||
function Workload ({ api, core, workload, workplan }: Props): React.ReactElement<Props> {
|
||||
function Workload ({ api, config, core, workload, workplan }: Props): React.ReactElement<Props> {
|
||||
const { isApiReady } = useApi();
|
||||
const salesInfo = useBrokerSalesInfo();
|
||||
const salesInfo = useBrokerSalesInfo(api, isApiReady);
|
||||
|
||||
const status = useCall<Option<PalletBrokerStatusRecord>>(isApiReady && api.query.broker?.status);
|
||||
const [isExpanded, toggleIsExpanded] = useToggle(false);
|
||||
@ -45,18 +46,19 @@ function Workload ({ api, core, workload, workplan }: Props): React.ReactElement
|
||||
|
||||
useEffect(() => {
|
||||
if (!!workload?.length && !!salesInfo) {
|
||||
setWorkloadData(formatRowInfo(workload, core, region, currentTimeSlice, salesInfo));
|
||||
// saleInfo points to a regionEnd and regionBeing in the next cycle, but we want the start and end of the current cycle
|
||||
setWorkloadData(formatRowInfo(workload, core, region, currentTimeSlice, { regionBegin: salesInfo.regionBegin - config.regionLength, regionEnd: salesInfo.regionEnd - config.regionLength }, config.regionLength));
|
||||
} else {
|
||||
return setWorkloadData([{ core }]);
|
||||
}
|
||||
}, [workload, region, currentTimeSlice, core, salesInfo]);
|
||||
}, [workload, region, currentTimeSlice, core, salesInfo, config]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!!workplan?.length && !!salesInfo) {
|
||||
setWorkplanData(formatRowInfo(workplan, core, region, currentTimeSlice, salesInfo));
|
||||
setWorkplanData(formatRowInfo(workplan, core, region, currentTimeSlice, salesInfo, config.regionLength));
|
||||
}
|
||||
}
|
||||
, [workplan, region, currentTimeSlice, core, salesInfo]);
|
||||
, [workplan, region, currentTimeSlice, core, salesInfo, config]);
|
||||
|
||||
const hasWorkplan = workplan?.length;
|
||||
|
||||
|
||||
@ -25,7 +25,7 @@ const formatDataObject = (one: CoreWorkplan | CoreWorkload, leaseMap: LeaseMapTy
|
||||
lastBlock: leaseMap[one?.info.task as number]?.until || 0,
|
||||
maskBits: one.info.maskBits,
|
||||
task: one.info.task,
|
||||
type: getOccupancyType(leaseMap[one.info.task as number], reservationMap[one.info.task as number])
|
||||
type: getOccupancyType(leaseMap[one.info.task as number], reservationMap[one.info.task as number], one.info.isPool)
|
||||
});
|
||||
|
||||
const formatData = (coreCount: number, workplan: CoreWorkplan[], workload: CoreWorkload[], leaseMap: LeaseMapType, reservationMap: ReservationMapType): CoreInfo[] => {
|
||||
@ -56,7 +56,7 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
const [data, setData] = useState<CoreInfo[]>([]);
|
||||
|
||||
const [filtered, setFiltered] = useState<CoreInfo[]>();
|
||||
const coreCount = useBrokerStatus('coreCount') || '-';
|
||||
const status = useBrokerStatus(api, isApiReady);
|
||||
|
||||
const workloadInfos: CoreWorkload[] | undefined = useWorkloadInfos(api, isApiReady);
|
||||
const workplanInfos: CoreWorkplan[] | undefined = useWorkplanInfos(api, isApiReady);
|
||||
@ -67,15 +67,15 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
const reservationMap: ReservationMapType = useMemo(() => reservations ? createTaskMap(reservations) : [], [reservations]);
|
||||
|
||||
useEffect(() => {
|
||||
!!workplanInfos && !!workloadInfos && !!coreCount &&
|
||||
setData(formatData(Number(coreCount), workplanInfos, workloadInfos, leaseMap, reservationMap));
|
||||
}, [workplanInfos, workloadInfos, leaseMap, reservationMap, coreCount]);
|
||||
!!workplanInfos && !!workloadInfos && !!status?.coreCount &&
|
||||
setData(formatData(Number(status?.coreCount), workplanInfos, workloadInfos, leaseMap, reservationMap));
|
||||
}, [workplanInfos, workloadInfos, leaseMap, reservationMap, status]);
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<Summary
|
||||
apiEndpoint={apiEndpoint}
|
||||
coreCount={coreCount}
|
||||
coreCount={status?.coreCount.toString() || '-'}
|
||||
workloadInfos={workloadInfos}
|
||||
></Summary>
|
||||
{!!data?.length &&
|
||||
@ -88,6 +88,7 @@ function Overview ({ className }: Props): React.ReactElement<Props> {
|
||||
<CoresTable
|
||||
api={api}
|
||||
data={filtered}
|
||||
isApiReady={isApiReady}
|
||||
/>
|
||||
)}
|
||||
</>)
|
||||
|
||||
@ -13,7 +13,7 @@ export interface InfoRow {
|
||||
owner?: string
|
||||
leaseLength?: number
|
||||
endBlock?: number
|
||||
type?: Occupancy
|
||||
type?: CoreTimeTypes
|
||||
}
|
||||
|
||||
export interface CoreInfo {
|
||||
@ -28,18 +28,19 @@ export interface statsType {
|
||||
tasks: number
|
||||
}
|
||||
|
||||
export enum Occupancy {
|
||||
export enum CoreTimeTypes {
|
||||
'Reservation',
|
||||
'Lease',
|
||||
'Bulk Coretime'
|
||||
'Bulk Coretime',
|
||||
'On Demand'
|
||||
}
|
||||
|
||||
export interface CoreWorkplanType extends CoreWorkplan {
|
||||
lastBlock: number,
|
||||
type: Occupancy
|
||||
type: CoreTimeTypes
|
||||
}
|
||||
|
||||
export interface CoreWorkloadType extends CoreWorkload {
|
||||
lastBlock: number,
|
||||
type: Occupancy
|
||||
type: CoreTimeTypes
|
||||
}
|
||||
|
||||
@ -6,12 +6,11 @@ import type { CoreWorkloadType, CoreWorkplanType, InfoRow } from './types.js';
|
||||
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
import { Occupancy } from './types.js';
|
||||
import { CoreTimeTypes } from './types.js';
|
||||
|
||||
export const CoreTimeConsts = {
|
||||
BlockTime: 6000,
|
||||
BlocksPerTimeslice: 80,
|
||||
DefaultRegion: 5040
|
||||
BlocksPerTimeslice: 80
|
||||
};
|
||||
|
||||
function formatDate (date: Date) {
|
||||
@ -23,18 +22,18 @@ function formatDate (date: Date) {
|
||||
}
|
||||
|
||||
/**
|
||||
* blockTime = 6000 ms
|
||||
* BlocksPerTimeslice = 80
|
||||
* Default Regoin = 5040 timeslices
|
||||
* TargetBlock = TargetTimeslice * BlocksPerTimeslice
|
||||
* Block Time Difference = |TargetBlock - latest Block| * blockTime
|
||||
*
|
||||
* Estimate timestamp =
|
||||
* if targetBlock is before the latestBlock
|
||||
* now minus block time difference
|
||||
* else
|
||||
* now plus block time difference
|
||||
*/
|
||||
* blockTime = 6000 ms
|
||||
* BlocksPerTimeslice = 80
|
||||
* Default Regoin = 5040 timeslices
|
||||
* TargetBlock = TargetTimeslice * BlocksPerTimeslice
|
||||
* Block Time Difference = |TargetBlock - latest Block| * blockTime
|
||||
*
|
||||
* Estimate timestamp =
|
||||
* if targetBlock is before the latestBlock
|
||||
* now minus block time difference
|
||||
* else
|
||||
* now plus block time difference
|
||||
*/
|
||||
export const estimateTime = (targetTimeslice: string | number, latestBlock: number): string | null => {
|
||||
if (!latestBlock || !targetTimeslice) {
|
||||
console.error('Invalid input: one or more inputs are missing');
|
||||
@ -61,15 +60,24 @@ export const estimateTime = (targetTimeslice: string | number, latestBlock: numb
|
||||
}
|
||||
};
|
||||
|
||||
export function formatRowInfo (data: CoreWorkloadType[] | CoreWorkplanType[], core: number, currentRegion: RegionInfo | undefined, timeslice: number, { regionBegin, regionEnd }: { regionBegin: number, regionEnd: number }, regionLength = CoreTimeConsts.DefaultRegion): InfoRow[] {
|
||||
/**
|
||||
*
|
||||
* @param data: CoreWorkloadType[]
|
||||
* @param core: core number
|
||||
* @param currentRegion
|
||||
* @param timeslice
|
||||
* @param param4
|
||||
* @param regionLength
|
||||
*/
|
||||
export function formatRowInfo (data: CoreWorkloadType[] | CoreWorkplanType[], core: number, currentRegion: RegionInfo | undefined, currentTimeSlice: number, { regionBegin, regionEnd }: { regionBegin: number, regionEnd: number }, regionLength: number): InfoRow[] {
|
||||
return data.map((one: CoreWorkloadType | CoreWorkplanType) => {
|
||||
const item: InfoRow = { core, maskBits: one?.info?.maskBits, task: one?.info?.task, type: one?.type };
|
||||
const blockNumber = timeslice * 80;
|
||||
const blockNumberNow = currentTimeSlice * 80;
|
||||
|
||||
item.type = one.type;
|
||||
let end; let start = null;
|
||||
|
||||
if (one.type === Occupancy.Lease) {
|
||||
if (one.type === CoreTimeTypes.Lease) {
|
||||
const period = Math.floor(one.lastBlock / regionLength);
|
||||
|
||||
end = period * regionLength;
|
||||
@ -79,12 +87,12 @@ export function formatRowInfo (data: CoreWorkloadType[] | CoreWorkplanType[], co
|
||||
}
|
||||
|
||||
item.owner = currentRegion?.owner.toString();
|
||||
item.start = start ? estimateTime(start, blockNumber) : null;
|
||||
item.end = end ? estimateTime(end, blockNumber) : null;
|
||||
item.start = start ? estimateTime(start, blockNumberNow) : null;
|
||||
item.end = end ? estimateTime(end, blockNumberNow) : null;
|
||||
item.endBlock = end ? Number(end) * 80 : 0;
|
||||
|
||||
if ('timeslice' in one && !start) {
|
||||
start = estimateTime(one.timeslice, blockNumber) ?? null;
|
||||
start = estimateTime(one.timeslice, blockNumberNow) ?? null;
|
||||
}
|
||||
|
||||
return item;
|
||||
@ -121,6 +129,10 @@ export const createTaskMap = <T extends { task: string }>(items: T[]): Record<nu
|
||||
}, {} as Record<number, T>);
|
||||
};
|
||||
|
||||
export const getOccupancyType = (lease: LegacyLease | undefined, reservation: Reservation | undefined): Occupancy => {
|
||||
return reservation ? Occupancy.Reservation : lease ? Occupancy.Lease : Occupancy['Bulk Coretime'];
|
||||
export const getOccupancyType = (lease: LegacyLease | undefined, reservation: Reservation | undefined, isPool: boolean): CoreTimeTypes => {
|
||||
if (isPool) {
|
||||
return CoreTimeTypes['On Demand'];
|
||||
}
|
||||
|
||||
return reservation ? CoreTimeTypes.Reservation : lease ? CoreTimeTypes.Lease : CoreTimeTypes['Bulk Coretime'];
|
||||
};
|
||||
|
||||
0
packages/page-coretime/.skip-build
Normal file
0
packages/page-coretime/.skip-build
Normal file
0
packages/page-coretime/.skip-npm
Normal file
0
packages/page-coretime/.skip-npm
Normal file
1
packages/page-coretime/README.md
Normal file
1
packages/page-coretime/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @polkadot/app-coretime
|
||||
27
packages/page-coretime/package.json
Normal file
27
packages/page-coretime/package.json
Normal file
@ -0,0 +1,27 @@
|
||||
{
|
||||
"bugs": "https://github.com/polkadot-js/apps/issues",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"homepage": "https://github.com/polkadot-js/apps/tree/master/packages/page-coretime#readme",
|
||||
"license": "Apache-2.0",
|
||||
"name": "@polkadot/app-coretime",
|
||||
"private": true,
|
||||
"repository": {
|
||||
"directory": "packages/page-coretime",
|
||||
"type": "git",
|
||||
"url": "https://github.com/polkadot-js/apps.git"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"version": "0.145.2-1-x",
|
||||
"dependencies": {
|
||||
"@polkadot/react-components": "^0.145.2-1-x",
|
||||
"@polkadot/react-query": "^0.145.2-1-x"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-dom": "*",
|
||||
"react-is": "*"
|
||||
}
|
||||
}
|
||||
90
packages/page-coretime/src/Overview/Summary.tsx
Normal file
90
packages/page-coretime/src/Overview/Summary.tsx
Normal file
@ -0,0 +1,90 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { BrokerStatus, CoreDescription, PalletBrokerConfigRecord, PalletBrokerSaleInfoRecord, RegionInfo } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useMemo } from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@polkadot/react-components';
|
||||
import { useCall } from '@polkadot/react-hooks';
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
import { estimateTime, FirstCycleStart } from '../utils.js';
|
||||
|
||||
interface Props {
|
||||
api: ApiPromise | null,
|
||||
coreDscriptors?: CoreDescription[];
|
||||
saleInfo: PalletBrokerSaleInfoRecord
|
||||
config: PalletBrokerConfigRecord,
|
||||
region: RegionInfo[],
|
||||
status: BrokerStatus,
|
||||
parachainCount: number
|
||||
}
|
||||
|
||||
function Summary ({ api, config, parachainCount, saleInfo, status }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const currentRegionEnd = saleInfo.regionEnd - config.regionLength;
|
||||
const currentRegionStart = saleInfo.regionEnd - config.regionLength * 2;
|
||||
const chainName = useCall<string>(api?.rpc.system.chain)?.toString().toLowerCase();
|
||||
|
||||
const cycleNumber = useMemo(() =>
|
||||
chainName && currentRegionEnd && Math.floor((currentRegionEnd - FirstCycleStart[chainName]) / config.regionLength)
|
||||
, [currentRegionEnd, chainName, config]);
|
||||
|
||||
return (
|
||||
<SummaryBox>
|
||||
<section>
|
||||
<CardSummary label={t('timeslice')}>
|
||||
{status?.lastTimeslice}
|
||||
</CardSummary>
|
||||
<CardSummary label={t('cores sold')}>
|
||||
{`${saleInfo?.coresSold} / ${saleInfo?.coresOffered}`}
|
||||
</CardSummary>
|
||||
<CardSummary label={t('parachains')}>
|
||||
{parachainCount && parachainCount}
|
||||
</CardSummary>
|
||||
{status &&
|
||||
<CardSummary label={t('cycle number')}>
|
||||
<div>
|
||||
{cycleNumber}
|
||||
</div>
|
||||
</CardSummary>
|
||||
}
|
||||
{config && status &&
|
||||
<CardSummary
|
||||
className='media--800'
|
||||
label={t('cycle progress')}
|
||||
progress={{
|
||||
isBlurred: false,
|
||||
total: new BN(config?.regionLength),
|
||||
value: new BN(config?.regionLength - (currentRegionEnd - status.lastTimeslice)),
|
||||
withTime: false
|
||||
}}
|
||||
/>
|
||||
}
|
||||
</section>
|
||||
<section className='media--1200'>
|
||||
{status &&
|
||||
(<CardSummary label={t('cycle dates')}>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{estimateTime(currentRegionStart, status?.lastTimeslice * 80)}</div>
|
||||
<div style={{ fontSize: '14px' }}>{estimateTime(currentRegionEnd, status?.lastTimeslice * 80)}</div>
|
||||
</div>
|
||||
</CardSummary>)
|
||||
}
|
||||
{status &&
|
||||
<CardSummary label={t('cycle ts')}>
|
||||
<div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionStart}</div>
|
||||
<div style={{ fontSize: '14px' }}>{currentRegionEnd}</div>
|
||||
</div>
|
||||
</CardSummary>
|
||||
}
|
||||
</section>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
79
packages/page-coretime/src/ParachainsTable.tsx
Normal file
79
packages/page-coretime/src/ParachainsTable.tsx
Normal file
@ -0,0 +1,79 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { FlagColor } from '@polkadot/react-components/types';
|
||||
import type { CoretimeInformation } from '@polkadot/react-hooks/types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { ParaLink, Table, Tag } from '@polkadot/react-components';
|
||||
import { BN, formatBalance, formatNumber } from '@polkadot/util';
|
||||
|
||||
import { useTranslation } from './translate.js';
|
||||
import { CoreTimeTypes } from './types.js';
|
||||
import { estimateTime, getOccupancyType } from './utils.js';
|
||||
|
||||
interface Props {
|
||||
coretimeInfo: CoretimeInformation
|
||||
}
|
||||
|
||||
const colours: Record<string, string> = {
|
||||
[CoreTimeTypes.Reservation]: 'orange',
|
||||
[CoreTimeTypes.Lease]: 'blue',
|
||||
[CoreTimeTypes['Bulk Coretime']]: 'pink'
|
||||
};
|
||||
|
||||
function ParachainsTable ({ coretimeInfo }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('parachains'), 'start'],
|
||||
[t('name'), 'start'],
|
||||
[t('core number'), 'start'],
|
||||
[t('type'), 'start'],
|
||||
[t('last block'), 'start'],
|
||||
[t('end'), 'start'],
|
||||
[t('renewal'), 'start'],
|
||||
[t('renewal price'), 'start']
|
||||
]);
|
||||
|
||||
return (
|
||||
<Table
|
||||
emptySpinner={false}
|
||||
header={headerRef.current}
|
||||
isSplit={false}
|
||||
>
|
||||
{coretimeInfo?.taskIds?.map((taskId: number) => {
|
||||
const chain = coretimeInfo.chainInfo[taskId];
|
||||
const type = getOccupancyType(chain?.lease, chain?.reservation, !!chain.worklplan?.find((a) => a.info.isPool));
|
||||
const targetTimeslice = chain?.lease?.until || coretimeInfo.salesInfo.regionEnd;
|
||||
const showEsimates = !!targetTimeslice && type !== CoreTimeTypes.Reservation;
|
||||
|
||||
return (
|
||||
<tr key={taskId}>
|
||||
<td>{taskId}</td>
|
||||
<td>
|
||||
<ParaLink
|
||||
id={new BN(taskId)}
|
||||
/>
|
||||
</td>
|
||||
<td>{chain?.workload?.core}</td>
|
||||
<td>
|
||||
<Tag
|
||||
color={colours[type] as FlagColor}
|
||||
label={Object.values(CoreTimeTypes)[type]}
|
||||
/>
|
||||
</td>
|
||||
<td>{showEsimates && formatNumber(targetTimeslice * 80).toString()}</td>
|
||||
<td>{showEsimates && estimateTime(targetTimeslice, coretimeInfo.status.lastCommittedTimeslice * 80)}</td>
|
||||
<td>{chain?.renewal ? 'eligible' : type === CoreTimeTypes['Bulk Coretime'] ? 'renewed' : ''}</td>
|
||||
<td>{chain?.renewal ? formatBalance(chain.renewal?.price.toString()) : ''}</td>
|
||||
</tr>
|
||||
);
|
||||
}
|
||||
)}
|
||||
|
||||
</Table>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(ParachainsTable);
|
||||
62
packages/page-coretime/src/index.tsx
Normal file
62
packages/page-coretime/src/index.tsx
Normal file
@ -0,0 +1,62 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { TabItem } from '@polkadot/react-components/types';
|
||||
|
||||
import React, { useRef } from 'react';
|
||||
|
||||
import { Tabs } from '@polkadot/react-components';
|
||||
import { useApi, useCoretimeInformation } from '@polkadot/react-hooks';
|
||||
|
||||
import Summary from './Overview/Summary.js';
|
||||
import ParachainsTable from './ParachainsTable.js';
|
||||
import { useTranslation } from './translate.js';
|
||||
|
||||
interface Props {
|
||||
basePath: string;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
function createItemsRef (t: (key: string, options?: { replace: Record<string, unknown> }) => string): TabItem[] {
|
||||
return [
|
||||
{
|
||||
isRoot: true,
|
||||
name: 'overview',
|
||||
text: t('Overview')
|
||||
}
|
||||
];
|
||||
}
|
||||
|
||||
function CoretimeApp ({ basePath, className }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const { api, isApiReady } = useApi();
|
||||
const itemsRef = useRef(createItemsRef(t));
|
||||
const coretimeInfo = useCoretimeInformation(api, isApiReady);
|
||||
|
||||
return (
|
||||
<main className={className}>
|
||||
<Tabs
|
||||
basePath={basePath}
|
||||
items={itemsRef.current}
|
||||
/>
|
||||
{coretimeInfo && (
|
||||
<Summary
|
||||
api={isApiReady ? api : null}
|
||||
config={coretimeInfo?.config}
|
||||
parachainCount={coretimeInfo.taskIds?.length || 0}
|
||||
region={coretimeInfo?.region}
|
||||
saleInfo={coretimeInfo?.salesInfo}
|
||||
status={coretimeInfo?.status}
|
||||
/>
|
||||
)}
|
||||
{!!coretimeInfo &&
|
||||
<ParachainsTable
|
||||
coretimeInfo={coretimeInfo}
|
||||
/>
|
||||
}
|
||||
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(CoretimeApp);
|
||||
8
packages/page-coretime/src/translate.ts
Normal file
8
packages/page-coretime/src/translate.ts
Normal file
@ -0,0 +1,8 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import { useTranslation as useTranslationBase } from 'react-i18next';
|
||||
|
||||
export function useTranslation (): { t: (key: string, options?: { replace: Record<string, unknown> }) => string } {
|
||||
return useTranslationBase('app-coretime');
|
||||
}
|
||||
9
packages/page-coretime/src/types.ts
Normal file
9
packages/page-coretime/src/types.ts
Normal file
@ -0,0 +1,9 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
export enum CoreTimeTypes {
|
||||
'Reservation',
|
||||
'Lease',
|
||||
'Bulk Coretime',
|
||||
'On Demand'
|
||||
}
|
||||
73
packages/page-coretime/src/utils.ts
Normal file
73
packages/page-coretime/src/utils.ts
Normal file
@ -0,0 +1,73 @@
|
||||
// Copyright 2017-2024 @polkadot/app-coretime authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { LegacyLease, Reservation } from '@polkadot/react-hooks/types';
|
||||
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
import { CoreTimeTypes } from './types.js';
|
||||
|
||||
export const CoreTimeConsts = {
|
||||
BlockTime: 6000,
|
||||
BlocksPerTimeslice: 80
|
||||
};
|
||||
|
||||
export const FirstCycleStart: Record<string, number> = {
|
||||
kusama: 285768,
|
||||
polkadot: 282525
|
||||
};
|
||||
|
||||
function formatDate (date: Date) {
|
||||
const day = date.getDate();
|
||||
const month = date.toLocaleString('default', { month: 'short' });
|
||||
const year = date.getFullYear();
|
||||
|
||||
return `${day} ${month} ${year}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* blockTime = 6000 ms
|
||||
* BlocksPerTimeslice = 80
|
||||
* Default Regoin = 5040 timeslices
|
||||
* TargetBlock = TargetTimeslice * BlocksPerTimeslice
|
||||
* Block Time Difference = |TargetBlock - latest Block| * blockTime
|
||||
*
|
||||
* Estimate timestamp =
|
||||
* if targetBlock is before the latestBlock
|
||||
* now minus block time difference
|
||||
* else
|
||||
* now plus block time difference
|
||||
*/
|
||||
export const estimateTime = (targetTimeslice: string | number, latestBlock: number): string | null => {
|
||||
if (!latestBlock || !targetTimeslice) {
|
||||
console.error('Invalid input: one or more inputs are missing');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
const now = new Date().getTime();
|
||||
|
||||
try {
|
||||
const blockTime = new BN(CoreTimeConsts.BlockTime); // Average block time in milliseconds (6 seconds)
|
||||
const timeSlice = new BN(CoreTimeConsts.BlocksPerTimeslice);
|
||||
const latestBlockBN = new BN(latestBlock);
|
||||
const timestampBN = new BN(now);
|
||||
const targetBlockBN = new BN(targetTimeslice).mul(timeSlice);
|
||||
const blockTimeDifference = targetBlockBN.sub(latestBlockBN).mul(blockTime);
|
||||
const estTimestamp = timestampBN.add(blockTimeDifference);
|
||||
|
||||
return formatDate(new Date(estTimestamp.toNumber()));
|
||||
} catch (error) {
|
||||
console.error('Error in calculation:', error);
|
||||
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
export const getOccupancyType = (lease: LegacyLease | undefined, reservation: Reservation | undefined, isPool: boolean): CoreTimeTypes => {
|
||||
if (isPool) {
|
||||
return CoreTimeTypes['On Demand'];
|
||||
}
|
||||
|
||||
return reservation ? CoreTimeTypes.Reservation : lease ? CoreTimeTypes.Lease : CoreTimeTypes['Bulk Coretime'];
|
||||
};
|
||||
13
packages/page-coretime/tsconfig.build.json
Normal file
13
packages/page-coretime/tsconfig.build.json
Normal file
@ -0,0 +1,13 @@
|
||||
{
|
||||
"extends": "../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"outDir": "./build",
|
||||
"rootDir": "./src"
|
||||
},
|
||||
"references": [
|
||||
{ "path": "../react-api/tsconfig.xref.json" },
|
||||
{ "path": "../react-hooks/tsconfig.build.json" },
|
||||
{ "path": "../react-components/tsconfig.build.json" }
|
||||
]
|
||||
}
|
||||
@ -18,7 +18,7 @@ import { deriveMapCache, setDeriveCache } from '@polkadot/api-derive/util';
|
||||
import { ethereumChains, typesBundle } from '@polkadot/apps-config';
|
||||
import { web3Accounts, web3Enable } from '@polkadot/extension-dapp';
|
||||
import { TokenUnit } from '@polkadot/react-components/InputConsts/units';
|
||||
import { useApiUrl, useEndpoint, usePeopleEndpoint, useQueue } from '@polkadot/react-hooks';
|
||||
import { useApiUrl, useCoretimeEndpoint, useEndpoint, usePeopleEndpoint, useQueue } from '@polkadot/react-hooks';
|
||||
import { ApiCtx } from '@polkadot/react-hooks/ctx/Api';
|
||||
import { ApiSigner } from '@polkadot/react-signer/signers';
|
||||
import { keyring } from '@polkadot/ui-keyring';
|
||||
@ -297,6 +297,7 @@ export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore
|
||||
const [isLocalFork] = useState(store.get('localFork') === apiUrl);
|
||||
const apiEndpoint = useEndpoint(apiUrl);
|
||||
const peopleEndpoint = usePeopleEndpoint(apiEndpoint?.relayName || apiEndpoint?.info);
|
||||
const coreTimeEndpoint = useCoretimeEndpoint(apiEndpoint?.relayName || apiEndpoint?.info);
|
||||
const relayUrls = useMemo(
|
||||
() => (apiEndpoint?.valueRelay && isNumber(apiEndpoint.paraId) && (apiEndpoint.paraId < 2000))
|
||||
? apiEndpoint.valueRelay
|
||||
@ -309,7 +310,14 @@ export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore
|
||||
: null,
|
||||
[apiEndpoint, peopleEndpoint]
|
||||
);
|
||||
const coretimeUrls = useMemo(
|
||||
() => (coreTimeEndpoint?.providers)
|
||||
? coreTimeEndpoint.providers
|
||||
: null,
|
||||
[coreTimeEndpoint]
|
||||
);
|
||||
const apiRelay = useApiUrl(relayUrls);
|
||||
const apiCoretime = useApiUrl(coretimeUrls);
|
||||
const apiSystemPeople = useApiUrl(peopleUrls);
|
||||
const createLink = useMemo(
|
||||
() => makeCreateLink(apiUrl, isElectron),
|
||||
@ -317,8 +325,8 @@ export function ApiCtxRoot ({ apiUrl, children, isElectron, store: keyringStore
|
||||
);
|
||||
const enableIdentity = apiEndpoint?.isPeople || (isNumber(apiEndpoint?.paraId) && (apiEndpoint?.paraId >= 2000)) || (typeof apiEndpoint?.isPeopleForIdentity === 'boolean' && !apiEndpoint?.isPeopleForIdentity);
|
||||
const value = useMemo<ApiProps>(
|
||||
() => objectSpread({}, state, { api: statics.api, apiEndpoint, apiError, apiIdentity: ((apiEndpoint?.isPeopleForIdentity && apiSystemPeople) || statics.api), apiRelay, apiSystemPeople, apiUrl, createLink, enableIdentity, extensions, isApiConnected, isApiInitialized, isElectron, isLocalFork, isWaitingInjected: !extensions }),
|
||||
[apiError, createLink, extensions, isApiConnected, isApiInitialized, isElectron, isLocalFork, state, apiEndpoint, apiRelay, apiUrl, apiSystemPeople, enableIdentity]
|
||||
() => objectSpread({}, state, { api: statics.api, apiCoretime, apiEndpoint, apiError, apiIdentity: ((apiEndpoint?.isPeopleForIdentity && apiSystemPeople) || statics.api), apiRelay, apiSystemPeople, apiUrl, createLink, enableIdentity, extensions, isApiConnected, isApiInitialized, isElectron, isLocalFork, isWaitingInjected: !extensions }),
|
||||
[apiError, createLink, extensions, isApiConnected, isApiInitialized, isElectron, isLocalFork, state, apiEndpoint, apiCoretime, apiRelay, apiUrl, apiSystemPeople, enableIdentity]
|
||||
);
|
||||
|
||||
// initial initialization
|
||||
|
||||
@ -53,6 +53,7 @@ export interface ApiProps extends ApiState {
|
||||
* Used for checking if tx.idenitity.* should be used. Can be used for other scenarios as well.
|
||||
*/
|
||||
enableIdentity: boolean;
|
||||
apiCoretime: ApiPromise;
|
||||
apiRelay: ApiPromise | null;
|
||||
apiSystemPeople: ApiPromise | null;
|
||||
apiUrl?: string;
|
||||
|
||||
@ -31,6 +31,7 @@ export interface ApiProps extends ApiState {
|
||||
apiEndpoint: LinkOption | null;
|
||||
apiError: string | null;
|
||||
apiIdentity: ApiPromise;
|
||||
apiCoretime: ApiPromise;
|
||||
enableIdentity: boolean;
|
||||
apiRelay: ApiPromise | null;
|
||||
apiSystemPeople: ApiPromise | null;
|
||||
|
||||
@ -33,6 +33,8 @@ export { useCallMulti } from './useCallMulti.js';
|
||||
export { useCollectiveInstance } from './useCollectiveInstance.js';
|
||||
export { useCollectiveMembers } from './useCollectiveMembers.js';
|
||||
export { useCoreDescriptor } from './useCoreDescriptor.js';
|
||||
export { useCoretimeEndpoint } from './useCoretimeEndpoint.js';
|
||||
export { useCoretimeInformation } from './useCoretimeInformation.js';
|
||||
export { useDebounce } from './useDebounce.js';
|
||||
export { useDelegations } from './useDelegations.js';
|
||||
export { useDeriveAccountFlags } from './useDeriveAccountFlags.js';
|
||||
|
||||
@ -220,6 +220,30 @@ export interface CoreDescription {
|
||||
info: PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor[];
|
||||
}
|
||||
|
||||
export interface CoreDescriptorAssignment {
|
||||
task: string,
|
||||
ratio: number,
|
||||
remaining: number,
|
||||
isTask: boolean,
|
||||
isPool: boolean
|
||||
}
|
||||
|
||||
export interface CoreDescriptor {
|
||||
core: number,
|
||||
info: {
|
||||
currentWork: {
|
||||
assignments: CoreDescriptorAssignment[],
|
||||
endHint: BN | null,
|
||||
pos: number,
|
||||
step: number
|
||||
},
|
||||
queue: {
|
||||
first: BN,
|
||||
last: BN
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface OnDemandQueueStatus {
|
||||
traffic: u128;
|
||||
nextIndex: u32;
|
||||
@ -297,3 +321,38 @@ export interface PalletBrokerConfigRecord {
|
||||
renewalBump: BN;
|
||||
contributionTimeout: number;
|
||||
}
|
||||
|
||||
export interface ChainInformation {
|
||||
id: number,
|
||||
workload: CoreWorkload | undefined,
|
||||
renewal: PotentialRenewal | undefined,
|
||||
worklplan: CoreWorkplan[] | undefined,
|
||||
lease: LegacyLease | undefined,
|
||||
reservation: Reservation| undefined
|
||||
}
|
||||
export interface CoretimeInformation {
|
||||
chainInfo: Record<number, ChainInformation>,
|
||||
salesInfo: PalletBrokerSaleInfoRecord,
|
||||
status: BrokerStatus,
|
||||
region: RegionInfo[],
|
||||
config: PalletBrokerConfigRecord
|
||||
taskIds: number[]
|
||||
}
|
||||
|
||||
export interface BrokerStatus {
|
||||
coreCount: number;
|
||||
privatePoolSize: number;
|
||||
systemPoolSize: number;
|
||||
lastCommittedTimeslice: number;
|
||||
lastTimeslice: number;
|
||||
}
|
||||
|
||||
export interface PotentialRenewal {
|
||||
core: number,
|
||||
when: number,
|
||||
price: BN,
|
||||
completion: 'Complete' | 'Partial',
|
||||
mask: string[]
|
||||
maskBits: number,
|
||||
task: string
|
||||
}
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletBrokerConfigRecord } from '@polkadot/types/lookup';
|
||||
import type { PalletBrokerConfigRecord as SimplifiedPalletBrokerConfigRecord } from './types.js';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { createNamedHook, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function extractInfo (config: Option<PalletBrokerConfigRecord>): SimplifiedPalletBrokerConfigRecord {
|
||||
const c = config.unwrap();
|
||||
@ -24,10 +25,8 @@ function extractInfo (config: Option<PalletBrokerConfigRecord>): SimplifiedPalle
|
||||
};
|
||||
}
|
||||
|
||||
function useBrokerConfigImpl () {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
const config = useCall<Option<PalletBrokerConfigRecord>>(isApiReady && api.query.broker.configuration);
|
||||
function useBrokerConfigImpl (api: ApiPromise, ready: boolean) {
|
||||
const config = useCall<Option<PalletBrokerConfigRecord>>(ready && api?.query.broker.configuration);
|
||||
|
||||
const [state, setState] = useState<SimplifiedPalletBrokerConfigRecord | undefined>();
|
||||
|
||||
|
||||
@ -11,7 +11,7 @@ import { useEffect, useState } from 'react';
|
||||
import { createNamedHook, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function useBrokerLeasesImpl (api: ApiPromise, ready: boolean): LegacyLease[] | undefined {
|
||||
const leases = useCall<Vec<PalletBrokerLeaseRecordItem>>(ready && api.query.broker.leases);
|
||||
const leases = useCall<Vec<PalletBrokerLeaseRecordItem>>(ready && api?.query?.broker?.leases);
|
||||
const [state, setState] = useState<LegacyLease[]>();
|
||||
|
||||
useEffect((): void => {
|
||||
|
||||
75
packages/react-hooks/src/useBrokerPotentialRenewals.ts
Normal file
75
packages/react-hooks/src/useBrokerPotentialRenewals.ts
Normal file
@ -0,0 +1,75 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Option, StorageKey, u32 } from '@polkadot/types';
|
||||
import type { PalletBrokerPotentialRenewalId, PalletBrokerPotentialRenewalRecord } from '@polkadot/types/lookup';
|
||||
import type { PotentialRenewal } from './types.js';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
|
||||
import { BN_ZERO } from '@polkadot/util';
|
||||
|
||||
import { processHexMask } from './utils/dataProcessing.js';
|
||||
|
||||
function extractInfo (info: Option<PalletBrokerPotentialRenewalRecord>, item: PalletBrokerPotentialRenewalId): PotentialRenewal | undefined {
|
||||
const unwrapped: PalletBrokerPotentialRenewalRecord | null = info.isSome ? info.unwrap() : null;
|
||||
let mask: string[] = [];
|
||||
let task = '';
|
||||
|
||||
if (!unwrapped) {
|
||||
return;
|
||||
}
|
||||
|
||||
const completion = unwrapped?.completion;
|
||||
|
||||
if (completion?.isComplete) {
|
||||
const complete = completion?.asComplete[0];
|
||||
|
||||
task = complete.assignment.isTask ? complete?.assignment.asTask.toString() : complete?.assignment.isPool ? 'Pool' : 'Idle';
|
||||
mask = processHexMask(complete.mask);
|
||||
} else if (completion?.isPartial) {
|
||||
mask = processHexMask(completion?.asPartial);
|
||||
task = '';
|
||||
} else {
|
||||
mask = [];
|
||||
}
|
||||
|
||||
return {
|
||||
// How much of a core has been assigned or, if completely assigned, the workload itself.
|
||||
completion: completion?.type,
|
||||
core: item?.core.toNumber(),
|
||||
mask,
|
||||
maskBits: mask?.length,
|
||||
price: unwrapped?.price.toBn() || BN_ZERO,
|
||||
task,
|
||||
when: item?.when.toNumber()
|
||||
};
|
||||
}
|
||||
|
||||
const OPT_KEY = {
|
||||
transform: (keys: StorageKey<[u32]>[]): u32[] =>
|
||||
keys.map(({ args: [id] }) => id)
|
||||
};
|
||||
|
||||
function useBrokerPotentialRenewalsImpl (api: ApiPromise, ready: boolean): PotentialRenewal[] | undefined {
|
||||
const keys = useMapKeys(ready && api?.query.broker.potentialRenewals, [], OPT_KEY);
|
||||
const potentialRenewals = useCall<[[PalletBrokerPotentialRenewalId[]], Option<PalletBrokerPotentialRenewalRecord>[]]>(ready && api?.query.broker.potentialRenewals.multi, [keys], { withParams: true });
|
||||
|
||||
const [state, setState] = useState<PotentialRenewal[] | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
if (!potentialRenewals) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renewals = potentialRenewals[0][0].map((info, index) => extractInfo(potentialRenewals[1][index], info));
|
||||
|
||||
setState(renewals.filter((renewal): renewal is PotentialRenewal => !!renewal));
|
||||
}, [potentialRenewals]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export const useBrokerPotentialRenewals = createNamedHook('useBrokerPotentialRenewals', useBrokerPotentialRenewalsImpl);
|
||||
@ -13,7 +13,7 @@ import { createNamedHook, useCall } from '@polkadot/react-hooks';
|
||||
import { processHexMask } from './utils/dataProcessing.js';
|
||||
|
||||
function useBrokerReservationsImpl (api: ApiPromise, ready: boolean): Reservation[] | undefined {
|
||||
const reservations = useCall<[any, Vec<Vec<PalletBrokerScheduleItem>>[]]>(ready && api.query.broker.reservations);
|
||||
const reservations = useCall<[any, Vec<Vec<PalletBrokerScheduleItem>>[]]>(ready && api?.query.broker.reservations);
|
||||
const [state, setState] = useState<Reservation[]>();
|
||||
|
||||
useEffect((): void => {
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletBrokerSaleInfoRecord } from '@polkadot/types/lookup';
|
||||
import type { PalletBrokerSaleInfoRecord as SimplifiedPalletBrokerSaleInfoRecord } from './types.js';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { createNamedHook, useCall } from '@polkadot/react-hooks';
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
function extractInfo (record: Option<PalletBrokerSaleInfoRecord>): SimplifiedPalletBrokerSaleInfoRecord {
|
||||
@ -27,10 +28,8 @@ function extractInfo (record: Option<PalletBrokerSaleInfoRecord>): SimplifiedPal
|
||||
};
|
||||
}
|
||||
|
||||
function useBrokerSalesInfoImpl () {
|
||||
const { api, isApiReady } = useApi();
|
||||
|
||||
const record = useCall<Option<PalletBrokerSaleInfoRecord>>(isApiReady && api.query.broker.saleInfo);
|
||||
function useBrokerSalesInfoImpl (api: ApiPromise, ready: boolean) {
|
||||
const record = useCall<Option<PalletBrokerSaleInfoRecord>>(ready && api?.query.broker.saleInfo);
|
||||
|
||||
const [state, setState] = useState<SimplifiedPalletBrokerSaleInfoRecord | undefined>();
|
||||
|
||||
|
||||
@ -1,25 +1,34 @@
|
||||
// Copyright 2017-2024 @polkadot/react-query authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { Option } from '@polkadot/types';
|
||||
import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup';
|
||||
import type { BrokerStatus } from './types.js';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
|
||||
import { createNamedHook, useCall } from '@polkadot/react-hooks';
|
||||
|
||||
function useBrokerStatusImpl (query: string): string | undefined {
|
||||
const { api } = useApi();
|
||||
const status = useCall<PalletBrokerStatusRecord>(api.query.broker?.status);
|
||||
const [state, setState] = useState<PalletBrokerStatusRecord | undefined>();
|
||||
function useBrokerStatusImpl (api: ApiPromise, ready: boolean): BrokerStatus | undefined {
|
||||
const status = useCall<Option<PalletBrokerStatusRecord>>(ready && api?.query.broker?.status);
|
||||
const [state, setState] = useState<BrokerStatus | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
status &&
|
||||
setState(
|
||||
status
|
||||
);
|
||||
if (!!status && status.isSome) {
|
||||
const s = status.unwrap();
|
||||
|
||||
setState({
|
||||
coreCount: s.coreCount?.toNumber(),
|
||||
lastCommittedTimeslice: s.lastCommittedTimeslice?.toNumber(),
|
||||
lastTimeslice: s.lastTimeslice?.toNumber(),
|
||||
privatePoolSize: s.privatePoolSize?.toNumber(),
|
||||
systemPoolSize: s.systemPoolSize?.toNumber()
|
||||
});
|
||||
}
|
||||
}, [status]);
|
||||
|
||||
return state?.toJSON()?.[query]?.toString();
|
||||
return state;
|
||||
}
|
||||
|
||||
export const useBrokerStatus = createNamedHook('useBrokerStatus', useBrokerStatusImpl);
|
||||
|
||||
@ -2,18 +2,42 @@
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { StorageKey, u32, Vec } from '@polkadot/types';
|
||||
import type { PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor } from '@polkadot/types/lookup';
|
||||
import type { CoreDescription } from './types.js';
|
||||
import type { StorageKey, u32 } from '@polkadot/types';
|
||||
import type { PalletBrokerCoretimeInterfaceCoreAssignment, PolkadotRuntimeParachainsAssignerCoretimeAssignmentState, PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor, PolkadotRuntimeParachainsAssignerCoretimeQueueDescriptor, PolkadotRuntimeParachainsAssignerCoretimeWorkState } from '@polkadot/types/lookup';
|
||||
import type { CoreDescriptor } from './types.js';
|
||||
|
||||
import { useEffect, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
|
||||
import { BN } from '@polkadot/util';
|
||||
|
||||
function extractInfo (info: PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor, core: number): CoreDescriptor {
|
||||
const currentWork: PolkadotRuntimeParachainsAssignerCoretimeWorkState | null = info?.currentWork.isSome ? info.currentWork.unwrap() : null;
|
||||
const queue: PolkadotRuntimeParachainsAssignerCoretimeQueueDescriptor | null = info?.queue.isSome ? info.queue.unwrap() : null;
|
||||
const assignments = currentWork?.assignments || [];
|
||||
|
||||
function extractInfo (info: PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor[], core: number) {
|
||||
return {
|
||||
core,
|
||||
info
|
||||
info: {
|
||||
currentWork: {
|
||||
assignments: assignments?.map((one: [PalletBrokerCoretimeInterfaceCoreAssignment, PolkadotRuntimeParachainsAssignerCoretimeAssignmentState]) => {
|
||||
return ({
|
||||
isPool: one[0]?.isPool,
|
||||
isTask: one[0]?.isTask,
|
||||
ratio: one[1]?.ratio.toNumber(),
|
||||
remaining: one[1]?.remaining.toNumber(),
|
||||
task: one[0]?.isTask ? one[0]?.asTask.toString() : one[0]?.isPool ? 'Pool' : 'Idle'
|
||||
});
|
||||
}),
|
||||
endHint: currentWork?.endHint.isSome ? currentWork?.endHint?.unwrap().toBn() : null,
|
||||
pos: currentWork?.pos.toNumber() || 0,
|
||||
step: currentWork?.step.toNumber() || 0
|
||||
},
|
||||
queue: {
|
||||
first: queue?.first.toBn() || new BN(0),
|
||||
last: queue?.last.toBn() || new BN(0)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@ -22,7 +46,7 @@ const OPT_KEY = {
|
||||
keys.map(({ args: [id] }) => id)
|
||||
};
|
||||
|
||||
function useCoreDescriptorImpl (api: ApiPromise, ready: boolean): CoreDescription[] | undefined {
|
||||
function useCoreDescriptorImpl (api: ApiPromise, ready: boolean): CoreDescriptor[] | undefined {
|
||||
const keys = useMapKeys(ready && api.query.coretimeAssignmentProvider.coreDescriptors, [], OPT_KEY);
|
||||
|
||||
const sanitizedKeys = keys?.map((_, index) => {
|
||||
@ -31,17 +55,12 @@ function useCoreDescriptorImpl (api: ApiPromise, ready: boolean): CoreDescriptio
|
||||
|
||||
sanitizedKeys?.pop();
|
||||
|
||||
const coreDescriptors = useCall<[[number[]], Vec<PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor>[]]>(ready && api.query.coretimeAssignmentProvider.coreDescriptors.multi, [sanitizedKeys], { withParams: true });
|
||||
|
||||
const [state, setState] = useState<CoreDescription[] | undefined>();
|
||||
const coreDescriptors = useCall<[[number[]], PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor[]]>(ready && api.query.coretimeAssignmentProvider.coreDescriptors.multi, [sanitizedKeys], { withParams: true });
|
||||
const [state, setState] = useState<CoreDescriptor[] | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
coreDescriptors &&
|
||||
setState(
|
||||
coreDescriptors[0][0].map((info, index) => {
|
||||
return extractInfo(coreDescriptors[1][index], info);
|
||||
}
|
||||
)
|
||||
setState(coreDescriptors[0][0].map((info, index) => extractInfo(coreDescriptors[1][index], info))
|
||||
);
|
||||
}, [coreDescriptors]);
|
||||
|
||||
|
||||
23
packages/react-hooks/src/useCoretimeEndpoint.ts
Normal file
23
packages/react-hooks/src/useCoretimeEndpoint.ts
Normal file
@ -0,0 +1,23 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
|
||||
|
||||
import { useMemo } from 'react';
|
||||
|
||||
import { createWsEndpoints } from '@polkadot/apps-config';
|
||||
import { isString } from '@polkadot/util';
|
||||
|
||||
import { createNamedHook } from './createNamedHook.js';
|
||||
|
||||
const endpoints = createWsEndpoints((k, v) => v?.toString() || k);
|
||||
|
||||
export function getCoretimeEndpoint (curApiInfo?: string): LinkOption | null {
|
||||
return endpoints.find(({ info }) => isString(info) && isString(curApiInfo) && info.toLowerCase().includes('coretime') && info.toLowerCase().includes(curApiInfo.toLowerCase())) || null;
|
||||
}
|
||||
|
||||
function useCoretimeEndpointImpl (relayInfo?: string): LinkOption | null {
|
||||
return useMemo(() => getCoretimeEndpoint(relayInfo), [relayInfo]);
|
||||
}
|
||||
|
||||
export const useCoretimeEndpoint = createNamedHook('useCoretimeEndpoint', useCoretimeEndpointImpl);
|
||||
115
packages/react-hooks/src/useCoretimeInformation.ts
Normal file
115
packages/react-hooks/src/useCoretimeInformation.ts
Normal file
@ -0,0 +1,115 @@
|
||||
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@polkadot/api';
|
||||
import type { ParaId } from '@polkadot/types/interfaces';
|
||||
import type { ChainInformation, CoretimeInformation, CoreWorkload, CoreWorkloadInfo, PotentialRenewal } from './types.js';
|
||||
|
||||
import { useEffect, useMemo, useState } from 'react';
|
||||
|
||||
import { createNamedHook, useApi, useBrokerConfig, useBrokerLeases, useBrokerReservations, useBrokerSalesInfo, useBrokerStatus, useCall, useCoreDescriptor, useRegions, useWorkloadInfos, useWorkplanInfos } from '@polkadot/react-hooks';
|
||||
|
||||
import { useBrokerPotentialRenewals } from './useBrokerPotentialRenewals.js';
|
||||
|
||||
function useCoretimeInformationImpl (api: ApiPromise, ready: boolean): CoretimeInformation | undefined {
|
||||
const { apiCoretime, isApiReady } = useApi();
|
||||
|
||||
const [workloadData, setWorkloadData] = useState<CoreWorkload[]>([]);
|
||||
const [taskIds, setTaskIds] = useState<number[]>([]);
|
||||
|
||||
/** coretime API calls */
|
||||
const status = useBrokerStatus(apiCoretime, isApiReady);
|
||||
const leases = useBrokerLeases(apiCoretime, isApiReady);
|
||||
const reservations = useBrokerReservations(apiCoretime, isApiReady);
|
||||
const salesInfo = useBrokerSalesInfo(apiCoretime, isApiReady);
|
||||
const workloads = useWorkloadInfos(apiCoretime, isApiReady);
|
||||
const workplans = useWorkplanInfos(apiCoretime, isApiReady);
|
||||
const config = useBrokerConfig(apiCoretime, isApiReady);
|
||||
const potentialRenewals = useBrokerPotentialRenewals(apiCoretime, isApiReady);
|
||||
const region = useRegions(apiCoretime);
|
||||
|
||||
/** Other APIs */
|
||||
const paraIds = useCall<ParaId[]>(api.query.paras.parachains);
|
||||
const coreInfos = useCoreDescriptor(api, ready);
|
||||
// when - The point in time that the renewable workload on core ends and a fresh renewal may begin.
|
||||
const potentialRenewalsCurrentRegion = useMemo(() => config && salesInfo && potentialRenewals?.filter((renewal: PotentialRenewal) => renewal.when.toString() === salesInfo?.regionBegin.toString()), [potentialRenewals, salesInfo, config]);
|
||||
const [state, setState] = useState<CoretimeInformation | undefined>();
|
||||
|
||||
useEffect(() => {
|
||||
if (paraIds?.length && potentialRenewals?.length && !taskIds.length) {
|
||||
const simpleIds = paraIds.map((p) => p.toNumber());
|
||||
const renewalIds = potentialRenewals?.map((r) => Number(r.task));
|
||||
const numbers = [...new Set(simpleIds.concat(renewalIds))];
|
||||
|
||||
if (numbers?.length > simpleIds.length) {
|
||||
setTaskIds(numbers.sort((a, b) => a - b));
|
||||
} else {
|
||||
setTaskIds(simpleIds);
|
||||
}
|
||||
}
|
||||
}, [potentialRenewals, paraIds, taskIds]);
|
||||
|
||||
useEffect(() => {
|
||||
if (workloads?.length) {
|
||||
setWorkloadData(workloads);
|
||||
}
|
||||
|
||||
if (!!coreInfos?.length && !workloads?.length) {
|
||||
const parsedCoreInfos = coreInfos?.map((coreInfo) => ({
|
||||
core: -1,
|
||||
info: coreInfo.info.currentWork.assignments.map((assignment) => (
|
||||
{
|
||||
isPool: assignment.isPool,
|
||||
isTask: assignment.isTask,
|
||||
mask: [],
|
||||
maskBits: 0,
|
||||
task: assignment.task
|
||||
}
|
||||
) as CoreWorkloadInfo)
|
||||
}));
|
||||
|
||||
setWorkloadData(parsedCoreInfos as unknown as CoreWorkload[]);
|
||||
}
|
||||
}, [workloads, coreInfos]);
|
||||
|
||||
useEffect((): void => {
|
||||
if (!workloadData?.length || !leases?.length || !reservations?.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chainInfo: Record<string, ChainInformation> = {};
|
||||
|
||||
taskIds?.forEach((id) => {
|
||||
const taskId = id.toString();
|
||||
const lease = leases?.find((lease) => lease.task === taskId);
|
||||
const reservation = reservations?.find((reservation) => reservation.task === taskId);
|
||||
const workload = workloadData?.find((one) => one.info.task === taskId);
|
||||
const renewal = potentialRenewalsCurrentRegion?.find((renewal) => renewal.task.toString() === taskId);
|
||||
const worklplan = workplans?.filter((workplan) => workplan.core === workload?.core && workplan.info.task.toString() === taskId);
|
||||
|
||||
chainInfo[id.toString()] = {
|
||||
id,
|
||||
lease,
|
||||
renewal,
|
||||
reservation,
|
||||
workload,
|
||||
worklplan
|
||||
};
|
||||
});
|
||||
|
||||
if (chainInfo && config && region && salesInfo && status) {
|
||||
setState({
|
||||
chainInfo,
|
||||
config,
|
||||
region,
|
||||
salesInfo,
|
||||
status,
|
||||
taskIds
|
||||
});
|
||||
}
|
||||
}, [taskIds, workloadData, potentialRenewalsCurrentRegion, salesInfo, leases, reservations, region, status, config, workplans]);
|
||||
|
||||
return state;
|
||||
}
|
||||
|
||||
export const useCoretimeInformation = createNamedHook('useCoretimeInformation', useCoretimeInformationImpl);
|
||||
@ -19,7 +19,7 @@ function extractInfo (value: OnDemandQueueStatus) {
|
||||
function useQueueStatusImpl (): OnDemandQueueStatus | undefined {
|
||||
const { api } = useApi();
|
||||
|
||||
const queue = useCall<OnDemandQueueStatus>(api.query.onDemandAssignmentProvider.queueStatus);
|
||||
const queue = useCall<OnDemandQueueStatus>(api.query.onDemandAssignmentProvider?.queueStatus);
|
||||
|
||||
const [state, setState] = useState<OnDemandQueueStatus | undefined>();
|
||||
|
||||
|
||||
@ -27,9 +27,9 @@ const OPT_KEY = {
|
||||
};
|
||||
|
||||
function useRegionsImpl (api: ApiPromise): RegionInfo[] | undefined {
|
||||
const regionKeys = useMapKeys(api.query.broker.regions, [], OPT_KEY);
|
||||
const regionKeys = useMapKeys(api?.query?.broker.regions, [], OPT_KEY);
|
||||
|
||||
const regionInfo = useCall<[[PalletBrokerRegionId[]], Option<PalletBrokerRegionRecord>[]]>(api.query.broker.regions.multi, [regionKeys], { withParams: true });
|
||||
const regionInfo = useCall<[[PalletBrokerRegionId[]], Option<PalletBrokerRegionRecord>[]]>(api?.query?.broker.regions.multi, [regionKeys], { withParams: true });
|
||||
|
||||
const [state, setState] = useState<RegionInfo[] | undefined>();
|
||||
|
||||
|
||||
@ -34,7 +34,7 @@ function extractInfo (info: PalletBrokerScheduleItem[], core: number): CoreWorkl
|
||||
isTask: assignment.isTask,
|
||||
mask,
|
||||
maskBits: mask.length,
|
||||
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : ''
|
||||
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : 'Idle'
|
||||
}
|
||||
};
|
||||
}
|
||||
@ -45,8 +45,8 @@ const OPT_KEY = {
|
||||
};
|
||||
|
||||
function useWorkloadInfosImpl (api: ApiPromise, ready: boolean): CoreWorkload[] | undefined {
|
||||
const cores = useMapKeys(ready && api.query.broker.workload, [], OPT_KEY);
|
||||
const workloadInfo = useCall<[[BN[]], Vec<PalletBrokerScheduleItem>[]]>(ready && api.query.broker.workload.multi, [cores], { withParams: true });
|
||||
const cores = useMapKeys(ready && api?.query.broker.workload, [], OPT_KEY);
|
||||
const workloadInfo = useCall<[[BN[]], Vec<PalletBrokerScheduleItem>[]]>(ready && api?.query.broker.workload.multi, [cores], { withParams: true });
|
||||
const [state, setState] = useState<CoreWorkload[] | undefined>();
|
||||
|
||||
useEffect((): void => {
|
||||
|
||||
@ -34,7 +34,7 @@ function extractInfo (info: Vec<PalletBrokerScheduleItem>, core: number, timesli
|
||||
isTask: assignment.isTask,
|
||||
mask,
|
||||
maskBits: mask.length,
|
||||
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : ''
|
||||
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : 'Idle'
|
||||
},
|
||||
timeslice
|
||||
};
|
||||
@ -46,13 +46,13 @@ const OPT_KEY = {
|
||||
};
|
||||
|
||||
function useWorkplanInfosImpl (api: ApiPromise, ready: boolean): CoreWorkplan[] | undefined {
|
||||
const workplanKeys = useMapKeys(ready && api.query.broker.workplan, [], OPT_KEY);
|
||||
const workplanKeys = useMapKeys(ready && api?.query.broker.workplan, [], OPT_KEY);
|
||||
|
||||
const sanitizedKeys = workplanKeys?.map((value) => {
|
||||
return value[0];
|
||||
});
|
||||
|
||||
const workplanInfo = useCall<[[[u32, u16][]], Option<Vec<PalletBrokerScheduleItem>>[]]>(ready && api.query.broker.workplan.multi, [sanitizedKeys], { withParams: true });
|
||||
const workplanInfo = useCall<[[[u32, u16][]], Option<Vec<PalletBrokerScheduleItem>>[]]>(ready && api?.query.broker.workplan.multi, [sanitizedKeys], { withParams: true });
|
||||
|
||||
const [state, setState] = useState<CoreWorkplan[] | undefined>();
|
||||
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// Copyright 2017-2024 @polkadot/react-query authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import React from 'react';
|
||||
|
||||
import { useBrokerStatus } from '@polkadot/react-hooks';
|
||||
|
||||
interface Props {
|
||||
children?: React.ReactNode;
|
||||
className?: string;
|
||||
query: string;
|
||||
}
|
||||
|
||||
function BrokerStatus ({ children, className = '', query }: Props): React.ReactElement<Props> {
|
||||
const info = useBrokerStatus(query) || '-';
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
{info}
|
||||
{children}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(BrokerStatus);
|
||||
@ -8,7 +8,6 @@ export { default as BestFinalized } from './BestFinalized.js';
|
||||
export { default as BestNumber } from './BestNumber.js';
|
||||
export { default as BlockToTime } from './BlockToTime.js';
|
||||
export { default as Bonded } from './Bonded.js';
|
||||
export { default as BrokerStatus } from './BrokerStatus.js';
|
||||
export { default as Chain } from './Chain.js';
|
||||
export { default as Elapsed } from './Elapsed.js';
|
||||
export { default as FormatBalance } from './FormatBalance.js';
|
||||
|
||||
@ -33,6 +33,7 @@
|
||||
"@polkadot/app-collator": ["page-collator/src/index.tsx"],
|
||||
"@polkadot/app-contracts": ["page-contracts/src/index.tsx"],
|
||||
"@polkadot/app-council": ["page-council/src/index.tsx"],
|
||||
"@polkadot/app-coretime": ["page-coretime/src/index.tsx"],
|
||||
"@polkadot/app-democracy": ["page-democracy/src/index.tsx"],
|
||||
"@polkadot/app-democracy/*": ["page-democracy/src/*.tsx"],
|
||||
"@polkadot/app-extrinsics": ["page-extrinsics/src/index.tsx"],
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
{ "path": "./packages/page-collator/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-contracts/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-council/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-coretime/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-democracy/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-explorer/tsconfig.build.json" },
|
||||
{ "path": "./packages/page-extrinsics/tsconfig.build.json" },
|
||||
|
||||
13
yarn.lock
13
yarn.lock
@ -1427,6 +1427,19 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@polkadot/app-coretime@workspace:packages/page-coretime":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@polkadot/app-coretime@workspace:packages/page-coretime"
|
||||
dependencies:
|
||||
"@polkadot/react-components": "npm:^0.145.2-1-x"
|
||||
"@polkadot/react-query": "npm:^0.145.2-1-x"
|
||||
peerDependencies:
|
||||
react: "*"
|
||||
react-dom: "*"
|
||||
react-is: "*"
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@polkadot/app-council@workspace:packages/page-council":
|
||||
version: 0.0.0-use.local
|
||||
resolution: "@polkadot/app-council@workspace:packages/page-council"
|
||||
|
||||
Loading…
Reference in New Issue
Block a user