Coretime Chain - Overview UI Tab (#10898)

* added overview page

* sanitized task

* added workplan table

* staged missing files

* updated filters

* fixed some types

* updated mask graph

* copyright

* linting

* install

* added relay overview

* linting

* refactoring hooks

* translations

* updated coretime deps

* updated tsconfig

* fixed build

* root tsconfig

* fix tsconfig

* app-coretime and app-broker

* filter bug fix

* prettied mask bar

* updated deps

* updated deps broker

* removed TS filter and added graph

* linting

* fixed headers

* fixed filter

* updated coretime summary

* updated relay tables

* fixed bulk price display

* updated regions

* styling

* fixed pallet version mismaatch info

* fixed expand button

* fixed alignment issues

* yarn install

* integrated some suggestions

* linting

* updated version

* Fix Header on table

* Small nit around naming

* Usage bar console err fix: NaN

* rearranged the layout for page broker on Coretime chain, changed UsageBar element to be more generic

* filtering parachains by id

* moved a type to a seprate type file

* skip build and npm

* generated locales

* added default ui colours to test chains and kusama

* abstracted logic from the rendering

* default ui highlight fallback

* table responsive - hiding timeslice info from the rows on mobile

* version updates for react-components and react-query

* exporting defaultHighlight

* responsive summary

* small fix for workplan filtering

* separated coretime chain page

* removed unused references

* added useBrokerReservations call and marked reserved chains

* useBrokerLease, added Legacy Lease type

* showing last block for legacy leases

* added processing at data retrieval

* fix: missing icon

* linting

* organized files, minor refactor

* moved filters out of the Overview componennt

* minor refactor

* version update

* regenerated yarn lock

* renamed column

* linter :)

* ts fixes :)

---------

Co-authored-by: bee344 <alberto.penayo@parity.io>
Co-authored-by: Alberto Nicolas Penayo <74352651+bee344@users.noreply.github.com>
Co-authored-by: tarikgul <tariksnow37@gmail.com>
This commit is contained in:
Daria 2024-09-10 20:30:41 +08:00 committed by GitHub
parent 20a8252411
commit 7c9f388698
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
56 changed files with 1965 additions and 20 deletions

View File

@ -957,6 +957,7 @@ export const prodParasKusamaCommon: EndpointOption[] = [
teleport: [-1],
text: 'Coretime',
ui: {
color: '#113911',
logo: chainsCoretimeKusamaSVG
}
},

View File

@ -720,7 +720,9 @@ export const testParasRococoCommon: EndpointOption[] = [
relayName: 'rococo',
teleport: [-1],
text: 'Coretime',
ui: {}
ui: {
color: '#f19135'
}
},
{
homepage: 'https://encointer.org/',

View File

@ -182,7 +182,9 @@ export const testParasWestendCommon: EndpointOption[] = [
relayName: 'westend',
teleport: [-1],
text: 'Coretime',
ui: {}
ui: {
color: '#f19135'
}
},
{
info: 'westendPeople',

View 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-broker';
export default function create (t: TFunction): Route {
return {
Component,
display: {
needsApi: [
'query.broker.status'
],
needsApiInstances: true
},
group: 'network',
icon: 'flask',
name: 'broker',
text: t('nav.broker', 'Coretime Broker (Experimental)', { ns: 'app-broker' })
};
}

View File

@ -9,6 +9,7 @@ import alliance from './alliance.js';
import ambassador from './ambassador.js';
import assets from './assets.js';
import bounties from './bounties.js';
import broker from './broker.js';
import calendar from './calendar.js';
import claims from './claims.js';
import collator from './collator.js';
@ -61,6 +62,7 @@ export default function create (t: TFunction): Routes {
// Legacy staking Pre v14 pallet version.
stakingLegacy(t),
collator(t),
broker(t),
// governance v2
referenda(t),
membership(t),

View File

@ -14,6 +14,7 @@
{ "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-council/tsconfig.build.json" },

View File

@ -0,0 +1,31 @@
{
"All active/available cores": "All active/available cores",
"All available slices": "All available slices",
"All scehduled cores": "All scehduled cores",
"No core description found": "No core description found",
"No workload found": "No workload found",
"No workplan found": "No workplan found",
"Overview": "Overview",
"assignment": "assignment",
"broker Id": "broker Id",
"core": "core",
"core count": "core count",
"current timeslice": "current timeslice",
"current work": "current work",
"estimated bulk price": "estimated bulk price",
"mask": "mask",
"nav.broker": "Coretime Broker (Experimental)",
"next index": "next index",
"parachain id": "parachain id",
"pool size": "pool size",
"region length": "region length",
"selected core": "selected core",
"selected core for workload": "selected core for workload",
"selected core for workplan": "selected core for workplan",
"timeslice": "timeslice",
"timeslice period": "timeslice period",
"traffic": "traffic",
"work queue": "work queue",
"workload": "workload",
"workplan": "workplan"
}

View File

@ -4,6 +4,7 @@
"app-alliance.json",
"app-assets.json",
"app-bounties.json",
"app-broker.json",
"app-calendar.json",
"app-claims.json",
"app-collator.json",

View File

@ -68,10 +68,13 @@
"Addresses": "",
"Advanced creation options": "",
"After delay": "",
"All active/available cores": "",
"All active/available tracks": "",
"All available slices": "",
"All bags": "",
"All pools": "",
"All rewards will go towards the selected output destination when a payout is made.": "",
"All scehduled cores": "",
"All stashes": "",
"All the listed validators and all their nominators will receive their rewards.": "",
"All validators": "",
@ -429,6 +432,7 @@
"No committee proposals": "",
"No completed campaigns found": "",
"No contracts available": "",
"No core description found": "",
"No council motions": "",
"No discretionary lock-voting is in place; all DOT used to vote counts the same.": "",
"No documentation provided": "",
@ -464,6 +468,8 @@
"No waiting validators found": "",
"No websites": "",
"No winners in this auction": "",
"No workload found": "",
"No workplan found": "",
"No, block all nominations": "",
"Node info": "",
"Nominate": "",
@ -1166,6 +1172,7 @@
"asset name": "",
"asset symbol": "",
"assets": "",
"assignment": "",
"at specific block": "",
"auctions": "",
"available signatories": "",
@ -1206,6 +1213,7 @@
"bounty remark": "",
"bounty requested allocation": "",
"bounty title": "",
"broker Id": "",
"bytes": "",
"bytes transferred": "",
"calculated storage fee": "",
@ -1263,6 +1271,8 @@
"conviction": "",
"conviction: Conviction": "",
"copied": "",
"core": "",
"core count": "",
"council candidates": "",
"council proposal type": "",
"count": "",
@ -1286,7 +1296,9 @@
"current range winning bid": "",
"current support (failing)": "",
"current support (passing)": "",
"current timeslice": "",
"current value": "",
"current work": "",
"currently elected": "",
"custom endpoint": "",
"decision deposit": "",
@ -1358,6 +1370,7 @@
"era {{era}}/unapplied": "",
"eras": "",
"errors": "",
"estimated bulk price": "",
"ethereum private key": "",
"event count": "",
"events": "",
@ -1474,6 +1487,7 @@
"logs": "",
"lowest / avg staked": "",
"manage hardware connections": "",
"mask": "",
"manage ledger app": "",
"matches": "",
"matrix name": "",
@ -1524,6 +1538,7 @@
"next": "",
"next action": "",
"next burn": "",
"next index": "",
"no": "",
"no addresses saved yet, add any existing address": "",
"no name": "",
@ -1576,6 +1591,7 @@
"period": "",
"points": "",
"pool id": "",
"pool size": "",
"pools": "",
"pot": "",
"preimage": "",
@ -1626,6 +1642,7 @@
"referendum id": "",
"refresh in": "",
"refund from account": "",
"region length": "",
"register from": "",
"registrar account": "",
"registrar index": "",
@ -1661,6 +1678,9 @@
"seed (hex or string)": "",
"select curator": "",
"selected constant query": "",
"selected core": "",
"selected core for workload": "",
"selected core for workplan": "",
"selected signatories": "",
"selected state query": "",
"selected track": "",
@ -1735,6 +1755,8 @@
"the supplied signature": "",
"threshold": "",
"timeout": "",
"timeslice": "",
"timeslice period": "",
"tip": "",
"tip amount": "",
"tip reason": "",
@ -1757,6 +1779,8 @@
"total sub": "",
"total transferable": "",
"track origin": "",
"traffic": "",
"traffic multiplier": "",
"transactions": "",
"transfer asset": "",
"transfer received": "",
@ -1841,6 +1865,9 @@
"with an index of": "",
"with capacity": "",
"with weight override": "",
"work queue": "",
"workload": "",
"workplan": "",
"yes": "",
"yesterday": "",
"your current password": "",

View File

@ -42,7 +42,10 @@
"Address Prefix": "Título de la dirección",
"Adjust the mode from basic (with a limited number of beginner-user-friendly apps) to full (with all basic & advanced apps available)": "Ajustar el modo desde lo básico (con un número limitado de aplicaciones para principiantes) a lo más completo (con todas las aplicaciones básicas y avanzadas disponibles)",
"Advanced creation options": "Opciones avanzadas para la creación",
"All active/available cores": "Todos los núcleos activos/disponibles",
"All available slices": "Todas las secciones disponibles",
"All rewards will go towards the selected output destination when a payout is made.": "Todas las recompensas irán hacia el destino de salida seleccionado cuando se efectúe el pago.",
"All scehduled cores": "Todos los núcleos agendados",
"All the listed validators and all their nominators will receive their rewards.": "Todos los validadores de la lista y todos sus nominadores recibirán su recompensa.",
"Allocate a suggested tip amount. With enough endorsements, the suggested values are averaged and sent to the beneficiary.": "Asigna una cantidad sugerida de propina. Con suficiente respaldo, los valores sugeridos serán un promedio y se enviarán al beneficiario.",
"Amount to add to the currently bonded funds. This is adjusted using the available funds on the account.": "Cantidad a añadir a los fondos actualmente en reserva. Se ajusta con los fondos disponibles en la cuenta.",
@ -241,6 +244,7 @@
"No code hashes available": "No hay hash de código disponible",
"No committee proposals": "No hay propuestas del comité",
"No contracts available": "No hay contrato disponible",
"No core description found": "No se ha encontrado descripción de núcleos",
"No council motions": "No hay mociones del consejo",
"No documentation provided": "No se ha facilitado documentación",
"No events available": "No hay eventos disponibles",
@ -259,6 +263,8 @@
"No runners up found": "No se han encontrado mensajeros",
"No upgradable extensions found": "No se han encontrado extensiones actualizables",
"No waiting validators found": "No se han encontrado validadores en espera",
"No workload found": "No se ha encontrado workload",
"No workplan found": "No se ha encontrado workplan",
"Node info": "Información del nodo",
"Nominate": "Nominar",
"Nominate Validators": "Nominar validadores",
@ -708,6 +714,7 @@
"approval type": "tipo de aprobación",
"approved": "aprobado",
"asset id": "id del activo",
"assignment": "asignación",
"auto-selected targets for nomination": "candidatos auto seleccionados para la nominación",
"available": "disponible",
"available signatories": "firmantes disponibles",
@ -729,6 +736,7 @@
"blocks": "bloques",
"bond": "vínculo",
"bonded": "vinculado",
"broker Id": "Id del Broker",
"call from account": "llamada desde la cuenta",
"call the selected endpoint": "llamar al punto final seleccionado",
"candidate account": "cuenta del candidato",
@ -755,12 +763,16 @@
"conviction": "sentencia",
"conviction: Conviction": "convicción: Convicción",
"copied": "copiado",
"core": "núcleo",
"core count": "cantidad de núcleos",
"council candidates": "candidatos al consejo",
"council proposal": "propuesta del consejo",
"council proposal type": "tipo de propuesta del consejo",
"created account": "cuenta creada",
"created multisig": "multisig creada",
"crypto type to use": "typo de crypto a usar",
"current timeslice": "rango de tiempo actual",
"current work": "trabajo actual",
"custom endpoint": "endpoint personalizado",
"data": "dato",
"default icon theme": "tema del icono por defecto",
@ -841,6 +853,7 @@
"locked balance": "balance bloqueado",
"logs": "logs",
"manage hardware connections": "manejar conexiones hardware",
"mask": "máscara",
"matches": "coincidencias",
"maximum gas allowed": "máximo de gas permitido",
"members": "miembros",
@ -883,6 +896,7 @@
"new address": "nueva dirección",
"next": "siguiente",
"next id": "siguiente ID",
"next index": "next index",
"no": "no",
"no accounts yet, create or import an existing": "no hay cuentas aún, cree o importe un existente",
"no addresses saved yet, add any existing address": "no hay direcciones almacenadas aún, añada cualquier dirección existente",
@ -916,6 +930,7 @@
"pending hashes": "hashes pendientes",
"pending swap id": "pendiente del ID de intercambio",
"points": "puntos",
"pool size": "Tamaño de pool",
"pot": "espacio",
"preimage hash": "hash de la preimgaen",
"prev": "previo",
@ -983,6 +998,8 @@
"seed (hex or string)": "semilla (hexadecimal o secuencia)",
"select the account you wish to sign data with": "seleccionar la cuenta que desea para firmar el dato",
"selected constant query": "consulta constante seleccionada",
"selected core for workload": "núcleo seleccionado para workload",
"selected core for workplan": "núcleo seleccionado para workplan",
"selected signatories": "firmantes seleccionados",
"selected state query": "consulta de estado seleccionado",
"selected validators": "validadores elegidos",
@ -1050,6 +1067,7 @@
"total peers": "pares totales",
"total stake": "stake total",
"total staked": "staked total",
"traffic": "tráfico",
"transactions": "transacciones",
"transfer received": "transferencia recibida",
"transferable": "transferible",
@ -1095,6 +1113,8 @@
"web": "web",
"website": "sitio web",
"with an index of": "con el índice de",
"work queue": "cola de trabajo",
"workload": "workload",
"wrong password supplied": "contraseña incorrecta suministrada",
"yes": "si",
"your current password": "su contraseña actual",

View File

View File

View File

@ -0,0 +1 @@
# @polkadot/app-broker

View 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-broker#readme",
"license": "Apache-2.0",
"name": "@polkadot/app-broker",
"private": true,
"repository": {
"directory": "packages/page-broker",
"type": "git",
"url": "https://github.com/polkadot-js/apps.git"
},
"sideEffects": false,
"type": "module",
"version": "0.143.3-21-x",
"dependencies": {
"@polkadot/react-components": "0.143.3-21-x",
"@polkadot/react-query": "0.143.3-21-x"
},
"peerDependencies": {
"react": "*",
"react-dom": "*",
"react-is": "*"
}
}

View File

@ -0,0 +1,54 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import React, { useRef } from 'react';
import { Table } from '@polkadot/react-components';
import { useTranslation } from '../translate.js';
import { type CoreWorkloadType, type CoreWorkplanType } from '../types.js';
import Workload from './Workload.js';
interface Props {
api: ApiPromise;
core: number;
workload?: CoreWorkloadType[],
workplan?: CoreWorkplanType[],
timeslice: number,
}
function CoreTable ({ api, core, timeslice, 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)?][] = [
[
<div key={`header${core}`}>{headerRef.current} {core} <span></span></div>,
'core',
8,
undefined
]
];
return (
<Table
emptySpinner={true}
header={header}
isSplit={false}
key={core}
>
{workload?.map((v) => (
<Workload
api={api}
key={v.core}
timeslice={timeslice}
value={v}
workplan={workplan}
/>
))}
</Table>
);
}
export default React.memo(CoreTable);

View File

@ -0,0 +1,47 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { CoreInfo, CoreWorkloadType, CoreWorkplanType } from '../types.js';
import React, { useMemo } from 'react';
import CoreTable from './CoreTable.js';
interface Props {
api: ApiPromise;
workloadInfos?: CoreWorkloadType[];
workplanInfos?: CoreWorkplanType[];
timeslice: number;
}
function CoresTable ({ api, timeslice, workloadInfos, workplanInfos }: Props): React.ReactElement<Props> {
const coreArr: number[] = useMemo(() => workloadInfos ? [...workloadInfos.map((plan) => plan.core)] : [], [workloadInfos]);
const filteredList: CoreInfo[] = useMemo(() => coreArr.map((c) => ({
core: c,
workload: workloadInfos?.filter((v) => v.core === c),
workplan: workplanInfos?.filter((v) => v.core === c)
})), [workloadInfos, workplanInfos, coreArr]);
return (
<>
{
filteredList.map((c) => {
return (
<CoreTable
api={api}
core={c.core}
key={`core ${c.core}`}
timeslice={timeslice}
workload={c.workload}
workplan={c.workplan}
/>
);
}
)
}
</>
);
}
export default React.memo(CoresTable);

View File

@ -0,0 +1,105 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { Dropdown, Input, styled } from '@polkadot/react-components';
import { useDebounce } from '@polkadot/react-hooks';
import { useTranslation } from '../translate.js';
import { type CoreWorkloadType, type CoreWorkplanType } from '../types.js';
const StyledDiv = styled.div`
@media (max-width: 768px) {
max-width: 100%:
}
`;
interface Props {
workLoad?: CoreWorkloadType[];
onFilter: (data: CoreWorkloadType[]) => void
}
const filterLoad = (parachainId: string, load: CoreWorkloadType[] | CoreWorkplanType[], workloadCoreSelected: number): CoreWorkloadType[] | CoreWorkplanType[] => {
if (parachainId) {
return load?.filter(({ info }) => info.task === parachainId);
}
if (workloadCoreSelected === -1) {
return load;
}
return load?.filter(({ core }) => core === workloadCoreSelected);
};
function Filters ({ onFilter, workLoad }: Props): React.ReactElement<Props> {
const [workloadCoreSelected, setWorkloadCoreSelected] = useState(-1);
const [_parachainId, setParachainId] = useState<string>('');
const coreArr: number[] = useMemo(() =>
workLoad?.length
? Array.from({ length: workLoad?.length || 0 }, (_, index) => index)
: []
, [workLoad]);
const { t } = useTranslation();
const parachainId = useDebounce(_parachainId);
const workloadCoreOpts = useMemo(
() => coreArr && [{ text: t('All active/available cores'), value: -1 }].concat(
coreArr
.map((c) => (
{
text: `Core ${c}`,
value: c
}
))
.filter((v): v is { text: string, value: number } => !!v.text)
),
[coreArr, t]
);
useEffect(() => {
if (!workLoad) {
return;
}
const filtered = filterLoad(parachainId, workLoad, workloadCoreSelected);
onFilter(filtered);
}, [workLoad, workloadCoreSelected, parachainId, onFilter]);
const onDropDownChange = useCallback((v: number) => {
setWorkloadCoreSelected(v);
setParachainId('');
}, []);
const onInputChange = useCallback((v: string) => {
setParachainId(v);
setWorkloadCoreSelected(-1);
}, []);
return (
<StyledDiv style={{ display: 'flex', flexDirection: 'column', gap: '1rem', marginBottom: '1.5rem', maxWidth: '300px' }}>
<Dropdown
className='isSmall'
label={t('selected core')}
onChange={onDropDownChange}
options={workloadCoreOpts}
value={workloadCoreSelected}
/>
<div style={{ minWidth: '150px' }}>
<Input
className='full isSmall'
label={t('parachain id')}
onChange={onInputChange}
placeholder={t('parachain id')}
value={_parachainId}
/>
</div>
</StyledDiv>
);
}
export default React.memo(Filters);

View File

@ -0,0 +1,93 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
import type { CoreWorkloadType, statsType } from '../types.js';
import React from 'react';
import { CardSummary, styled, SummaryBox, UsageBar } from '@polkadot/react-components';
import { defaultHighlight } from '@polkadot/react-components/styles';
import { useApi, useBrokerStatus, useCurrentPrice, useRenewalBump } from '@polkadot/react-hooks';
import { useTranslation } from '../translate.js';
import { getStats } from '../utils.js';
import Cores from './Summary/Cores.js';
import RegionLength from './Summary/RegionLength.js';
import RenewalPrice from './Summary/RenewalPrice.js';
import Timeslice from './Summary/Timeslice.js';
import TimeslicePeriod from './Summary/TimeslicePeriod.js';
const StyledDiv = styled.div`
display: flex;
flex-wrap: wrap;
gap: 1rem;
`;
const StyledSection = styled.section`
display: flex;
gap: 1rem;
@media (max-width: 768px) {
flex-direction: column;
margin-bottom: 2rem
}
`;
interface Props {
apiEndpoint?: LinkOption | null;
workloadInfos?: CoreWorkloadType[] | CoreWorkloadType
}
function Summary ({ workloadInfos }: Props): React.ReactElement {
const { t } = useTranslation();
const { api, apiEndpoint } = useApi();
const renewalBump = useRenewalBump();
const currentPrice = useCurrentPrice();
const totalCores = useBrokerStatus('coreCount');
const uiHighlight = apiEndpoint?.ui.color || defaultHighlight;
const { idles, pools, tasks }: statsType = React.useMemo(() => getStats(totalCores, workloadInfos), [totalCores, workloadInfos]);
return (
<SummaryBox>
<StyledSection style={{ display: 'flex' }}>
{api.query.broker && (
<>
<StyledDiv>
<CardSummary label={t('current timeslice')}>
<Timeslice />
</CardSummary>
<CardSummary label={t('core count')}>
<Cores />
</CardSummary>
<CardSummary label={t('timeslice period')}>
<TimeslicePeriod />
</CardSummary>
<CardSummary label={t('region length')}>
<RegionLength />
</CardSummary>
<CardSummary label={t('estimated bulk price')}>
<RenewalPrice
currentPrice={currentPrice}
renewalBump={renewalBump}
/>
</CardSummary>
</StyledDiv>
</>
)}
<div style={{ marginLeft: '2rem' }}>
<UsageBar
data={[
{ color: '#04AA6D', label: 'Pools', value: idles },
{ color: '#FFFFFF', label: 'Idle', value: pools },
{ color: uiHighlight, label: 'Tasks', value: tasks }]
}
></UsageBar>
</div>
</StyledSection>
</SummaryBox>
);
}
export default React.memo(Summary);

View File

@ -0,0 +1,24 @@
// 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);

View File

@ -0,0 +1,28 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerConfigRecord } from '@polkadot/types/lookup';
import React from 'react';
import { useApi, useCall } from '@polkadot/react-hooks';
interface Props {
className?: string;
children?: React.ReactNode;
}
function RegionLength ({ children, className }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const config = useCall<PalletBrokerConfigRecord>(api.query.broker?.configuration);
const length = config?.toJSON().regionLength;
return (
<div className={className}>
{length?.toString()}
{children}
</div>
);
}
export default React.memo(RegionLength);

View File

@ -0,0 +1,30 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { formatBalance } from '@polkadot/util';
interface Props {
className?: string;
children?: React.ReactNode;
renewalBump?: string;
currentPrice?: string;
}
function RenewalPrice ({ currentPrice, renewalBump }: Props): React.ReactElement<Props> | null {
const percentage = renewalBump ? Number.parseInt(renewalBump) / 1000000000 : 0;
const price = currentPrice ? Number.parseInt(currentPrice) : 0;
const renewalPrice = price * percentage + price;
return (
<div className='ui--balance-value'>
{formatBalance(renewalPrice)}
</div>
);
}
export default React.memo(RenewalPrice);

View File

@ -0,0 +1,24 @@
// 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 Timeslice ({ children, className }: Props): React.ReactElement<Props> | null {
return (
<BrokerStatus
className={className}
query='lastTimeslice'
>
{children}
</BrokerStatus>
);
}
export default React.memo(Timeslice);

View File

@ -0,0 +1,27 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { u32 } from '@polkadot/types';
import React from 'react';
import { useApi } from '@polkadot/react-hooks';
interface Props {
className?: string;
children?: React.ReactNode;
}
function BrokerId ({ children, className }: Props): React.ReactElement<Props> | null {
const { api } = useApi();
const period = api.consts.broker?.timeslicePeriod as u32;
return (
<div className={className}>
{period?.toString()}
{children}
</div>
);
}
export default React.memo(BrokerId);

View File

@ -0,0 +1,141 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { AddressMini, styled } from '@polkadot/react-components';
import { type InfoRow, Occupancy } from '../types.js';
const StyledTableCol = styled.td<{ hide?: 'mobile' | 'tablet' | 'both' }>`
width: 150px;
vertical-align: top;
@media (max-width: 768px) {
/* Mobile */
${(props) => props.hide === 'mobile' || props.hide === 'both' ? 'display: none;' : ''}
}
@media (min-width: 769px) and (max-width: 1024px) {
/* Tablet */
${(props) => props.hide === 'tablet' || props.hide === 'both' ? 'display: none;' : ''}
}
`;
const TableCol = ({ header,
hide,
value }: {
header: string;
value: string | number | null | undefined;
hide?: 'mobile' | 'tablet' | 'both';
}) => (
<StyledTableCol hide={hide}>
<h5 style={{ opacity: '0.6' }}>{header}</h5>
<p>{value || <>&nbsp;</>}</p>
</StyledTableCol>
);
function WorkInfoRow ({ data }: { data: InfoRow }): React.ReactElement {
if (!data.task) {
return (
<>
<td style={{ width: 200 }}>no task assigned</td>
<td colSpan={5} />
</>);
}
switch (data.type) {
case (Occupancy.Reservation): {
return (
<>
<TableCol
header='Task'
value={data.task}
/>
<TableCol
header='Blocks per timeslice'
value={data.maskBits}
/>
<TableCol
header='type'
value={'Reservation'}
/>
<td colSpan={3} />
</>
);
}
case (Occupancy.Lease): {
return (
<>
<TableCol
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}
/>
<TableCol
header='type'
value={'Legacy Lease'}
/>
</>);
}
default: {
return <>
<TableCol
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' }}>{'Owner'}</h5>
{data.owner
? (
<AddressMini
isPadded={false}
key={data.owner}
value={data.owner}
/>
)
: <p>&nbsp;</p>}
</StyledTableCol></>;
}
}
}
export default React.memo(WorkInfoRow);

View File

@ -0,0 +1,85 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { RegionInfo } from '@polkadot/react-hooks/types';
import type { CoreWorkloadType, CoreWorkplanType, InfoRow } from '../types.js';
import React, { useEffect, useState } from 'react';
import { ExpandButton } from '@polkadot/react-components';
import { useRegions, useToggle } from '@polkadot/react-hooks';
import { formatRowInfo } from '../utils.js';
import WorkInfoRow from './WorkInfoRow.js';
import Workplan from './Workplan.js';
interface Props {
api: ApiPromise;
value: CoreWorkloadType
timeslice: number;
workplan?: CoreWorkplanType[] | null
}
function Workload ({ api, timeslice, value: { core, info, lastBlock, type }, workplan }: Props): React.ReactElement<Props> {
const [isExpanded, toggleIsExpanded] = useToggle(false);
const [tableData, setTableData] = useState<InfoRow>();
const [currentRegion, setCurrentRegion] = useState<RegionInfo | undefined>();
const regionInfo = useRegions(api);
useEffect(() => {
if (info) {
const region: RegionInfo | undefined = regionInfo?.find((v) => v.core === core && v.start <= timeslice && v.end > timeslice);
setTableData(formatRowInfo(info, core, region, timeslice, type, lastBlock));
setCurrentRegion(region);
}
}, [info, regionInfo, core, timeslice, lastBlock, type]);
const hasWorkplan = !!workplan?.length;
return (
<>
{tableData &&
<tr
className={`isExpanded isFirst ${isExpanded ? '' : 'isLast'}`}
key={tableData.core}
>
<WorkInfoRow data={tableData} />
<td style={{ paddingRight: '2rem', textAlign: 'right', verticalAlign: 'top' }}>
<h5 style={{ opacity: '0.6' }}>Workplan ({workplan?.length})</h5>
{hasWorkplan &&
(
<ExpandButton
expanded={isExpanded}
onClick={toggleIsExpanded}
/>
)
}
{!hasWorkplan && 'none'}
</td>
</tr>
}
{isExpanded &&
<>
<tr>
<td style={{ fontWeight: 700, paddingTop: '2rem', width: 150 }}>workplans</td>
<td colSpan={6}></td>
</tr>
{workplan?.map((workplanInfo) => (
<Workplan
currentTimeSlice={timeslice}
isExpanded={isExpanded}
key={workplanInfo.core}
region={currentRegion}
value={workplanInfo}
/>
))}
</>
}
</>
);
}
export default React.memo(Workload);

View File

@ -0,0 +1,56 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { RegionInfo } from '@polkadot/react-hooks/types';
import type { CoreWorkplanType, InfoRow } from '../types.js';
import React, { useMemo } from 'react';
import { Spinner } from '@polkadot/react-components';
import { formatRowInfo } from '../utils.js';
import WorkInfoRow from './WorkInfoRow.js';
interface Props {
className?: string;
value: CoreWorkplanType;
currentTimeSlice: number
isExpanded: boolean
region: RegionInfo | undefined
}
function Workplan ({ currentTimeSlice, isExpanded, region, value: { core, info, lastBlock, type } }: Props): React.ReactElement<Props> {
const tableData: InfoRow = useMemo(() => {
return formatRowInfo(info, core, region, currentTimeSlice, type, lastBlock);
}, [info, core, region, currentTimeSlice, type, lastBlock]);
if (!tableData) {
return (
<tr
className={` ${isExpanded ? 'isExpanded isLast' : 'isCollapsed'}`}
style={{ minHeight: '100px' }}
>
<td> <Spinner /> </td>
<td colSpan={6}></td>
</tr>
);
}
return (
<>
{tableData && (
<tr
className={` ${isExpanded ? 'isExpanded isLast' : 'isCollapsed'}`}
key={tableData.core}
style={{ minHeight: '100px' }}
>
<WorkInfoRow data={tableData} />
<td />
</tr>
)}
</>
);
}
export default React.memo(Workplan);

View File

@ -0,0 +1,95 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { LinkOption } from '@polkadot/apps-config/endpoints/types';
import type { CoreWorkload, CoreWorkplan, LegacyLease, Reservation } from '@polkadot/react-hooks/types';
import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup';
import React, { useEffect, useMemo, useState } from 'react';
import { useApi, useBrokerLeases, useBrokerReservations, useCall, useWorkloadInfos, useWorkplanInfos } from '@polkadot/react-hooks';
import { type CoreWorkloadType, type CoreWorkplanType } from '../types.js';
import { createTaskMap, getOccupancyType } from '../utils.js';
import CoresTable from './CoresTables.js';
import Filters from './Filters.js';
import Summary from './Summary.js';
interface Props {
className?: string;
apiEndpoint?: LinkOption | null;
api: ApiPromise;
isReady: boolean;
}
const formatLoad = (data: CoreWorkplan[] | CoreWorkload[] | undefined, leaseMap: Record<number, LegacyLease>, reservationMap: Record<number, Reservation>): CoreWorkloadType[] | CoreWorkplanType[] => {
if (!data) {
return [];
}
return data.map((w: CoreWorkload | CoreWorkplan) => {
const result = {
...w,
lastBlock: leaseMap[w.info.task as number]?.until || 0,
type: getOccupancyType(leaseMap[w.info.task as number], reservationMap[w.info.task as number])
};
if ('timeslice' in w) {
return result as CoreWorkplanType;
} else {
return result as CoreWorkloadType;
}
});
};
function Overview ({ api, apiEndpoint, className, isReady }: Props): React.ReactElement<Props> {
const [workLoad, setWorkLoad] = useState<CoreWorkloadType[] | undefined>();
const [workPlan, setWorkPlan] = useState<CoreWorkplanType[]>();
const [filtered, setFiltered] = useState<CoreWorkloadType[]>();
const { isApiReady } = useApi();
const status = useCall<PalletBrokerStatusRecord>(isReady && api.query.broker?.status);
const workloadInfos: CoreWorkload[] | undefined = useWorkloadInfos(api, isApiReady);
const workplanInfos: CoreWorkplan[] | undefined = useWorkplanInfos(api, isApiReady);
const reservations: Reservation[] | undefined = useBrokerReservations(api, isApiReady);
const leases: LegacyLease[] | undefined = useBrokerLeases(api, isApiReady);
const leaseMap = useMemo(() => leases ? createTaskMap(leases) : [], [leases]);
const reservationMap = useMemo(() => reservations ? createTaskMap(reservations) : [], [reservations]);
const timesliceAsString = useMemo(() => {
const timeslice = status?.toHuman().lastCommittedTimeslice?.toString();
return timeslice === undefined ? '' : timeslice.toString().split(',').join('');
}, [status]);
useEffect(() =>
setWorkPlan(formatLoad(workplanInfos, leaseMap, reservationMap))
, [workplanInfos, leaseMap, reservationMap]);
useEffect(() => {
setWorkLoad(formatLoad(workloadInfos, leaseMap, reservationMap));
}, [workloadInfos, leaseMap, reservationMap]);
return (
<div className={className}>
<Summary
apiEndpoint={apiEndpoint}
workloadInfos={workLoad}
></Summary>
<Filters
onFilter={setFiltered}
workLoad={workLoad}
/>
<CoresTable
api={api}
timeslice={Number(timesliceAsString)}
workloadInfos={filtered || workLoad}
workplanInfos={workPlan}
/>
</div>
);
}
export default React.memo(Overview);

View File

@ -0,0 +1,49 @@
// Copyright 2017-2024 @polkadot/app-broker 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 } from '@polkadot/react-hooks';
import Overview from './Overview/index.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 BrokerApp ({ basePath, className }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const itemsRef = useRef(createItemsRef(t));
const { api, apiEndpoint, isApiReady } = useApi();
return (
<main className={className}>
<Tabs
basePath={basePath}
items={itemsRef.current}
/>
<Overview
api={api}
apiEndpoint={apiEndpoint}
isReady={isApiReady}
/>
</main>
);
}
export default React.memo(BrokerApp);

View File

@ -0,0 +1,8 @@
// Copyright 2017-2024 @polkadot/app-broker 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-broker');
}

View File

@ -0,0 +1,45 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { CoreWorkplan } from '@polkadot/react-hooks/types';
export interface InfoRow {
task: string | number,
maskBits: number,
core: number
mask?: string
start?: string | null,
end?: string | null
owner?: string
leaseLength?: number
endBlock?: number
type?: Occupancy
}
export interface CoreInfo {
core: number,
workload: CoreWorkloadType[] | undefined,
workplan: CoreWorkplanType[] | undefined
}
export interface statsType {
idles: number,
pools: number,
tasks: number
}
export enum Occupancy {
'Reservation',
'Lease',
'Rent'
}
export interface CoreWorkplanType extends CoreWorkplan {
lastBlock: number,
type: Occupancy
}
export interface CoreWorkloadType extends CoreWorkplan {
lastBlock: number,
type: Occupancy
}

View File

@ -0,0 +1,115 @@
// Copyright 2017-2024 @polkadot/app-broker authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { CoreWorkloadInfo, LegacyLease, RegionInfo, Reservation } from '@polkadot/react-hooks/types';
import type { CoreWorkloadType, InfoRow } from './types.js';
import { BN } from '@polkadot/util';
import { Occupancy } from './types.js';
const CoreTimeConsts = {
BlockTime: 6000,
BlocksPerTimeslice: 80,
DefaultRegion: 5040
};
function formatDate (date: Date) {
const day = date.getDate();
const month = date.toLocaleString('default', { month: 'short' });
const year = date.getFullYear();
return `${day} ${month} ${year}`;
}
export const estimateTime = (targetBlock: string, latestBlock: number, timestamp: number): string | null => {
if (!timestamp || !latestBlock || !targetBlock) {
console.error('Invalid input: one or more inputs are missing');
return null;
}
try {
const blockTime = new BN(CoreTimeConsts.BlockTime); // Average block time in milliseconds (6 seconds)
const timeSlice = new BN(CoreTimeConsts.BlocksPerTimeslice);
const targetBlockBN = new BN(targetBlock).mul(timeSlice);
const latestBlockBN = new BN(latestBlock);
const timestampBN = new BN(timestamp);
const blockDifference = targetBlockBN.sub((latestBlockBN)).abs().mul(blockTime);
const estTimestamp = targetBlockBN.lt(latestBlockBN)
? timestampBN.sub(blockDifference)
: timestampBN.add(blockDifference);
return formatDate(new Date(estTimestamp.toNumber()));
} catch (error) {
console.error('Error in calculation:', error);
return null;
}
};
export function formatRowInfo (info: CoreWorkloadInfo, core: number, currentRegion: RegionInfo | undefined, timeslice: number, type: Occupancy, lastBlock: number, regionLength = CoreTimeConsts.DefaultRegion): InfoRow {
const item: InfoRow = { core, maskBits: info.maskBits, task: info.task };
if (currentRegion) {
const start = currentRegion?.start?.toString() ?? 0;
const end = currentRegion?.end?.toString() ?? 0;
const blockNumber = timeslice * 80;
item.start = estimateTime(start, blockNumber, new Date().getTime());
item.end = estimateTime(end, blockNumber, new Date().getTime());
item.endBlock = Number(end) * 80;
item.owner = currentRegion?.owner.toString();
}
item.type = type;
if (lastBlock) {
const blockNumber = timeslice * 80;
const period = Math.floor(lastBlock / regionLength);
const end = period * regionLength;
item.start = ' - ';
item.end = estimateTime(end.toString(), blockNumber, new Date().getTime());
item.endBlock = Number(end) * 80;
}
return item;
}
export function getStats (totalCores: string | undefined, workloadInfos: CoreWorkloadType[] | CoreWorkloadType | undefined) {
if (!totalCores || !workloadInfos) {
return { idles: 0, pools: 0, tasks: 0 };
}
const sanitized: CoreWorkloadType[] = Array.isArray(workloadInfos) ? workloadInfos : [workloadInfos];
const { pools, tasks } = sanitized.reduce(
(acc, { info }) => {
if (info.isTask) {
acc.tasks += 1;
} else if (info.isPool) {
acc.pools += 1;
}
return acc;
},
{ pools: 0, tasks: 0 }
);
const idles = Number(totalCores) - (pools + tasks);
return { idles, pools, tasks };
}
export const createTaskMap = <T extends { task: string }>(items: T[]): Record<number, T> => {
return (items || []).reduce((acc, item) => {
acc[Number(item.task)] = item;
return acc;
}, {} as Record<number, T>);
};
export const getOccupancyType = (lease: LegacyLease | undefined, reservation: Reservation | undefined): Occupancy => {
return reservation ? Occupancy.Reservation : lease ? Occupancy.Lease : Occupancy.Rent;
};

View 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" }
]
}

View File

@ -0,0 +1,107 @@
// Copyright 2017-2024 @polkadot/react-components authors & contributors
// SPDX-License-Identifier: Apache-2.0
import React from 'react';
import { styled } from '@polkadot/react-components';
interface PieChartProps {
data: { label: string; value: number; color: string }[];
}
const Container = styled.div`
display: flex;
align-items: center;
`;
const GraphContainer = styled.div`
position: relative;
`;
const LegendContainer = styled.div`
display: flex;
flex-direction: column;
margin-left: 20px;
`;
const LegendItem = styled.div`
display: flex;
align-items: center;
margin-bottom: 10px;
`;
const ColorBox = styled.div<{ color: string }>`
width: 20px;
height: 20px;
background-color: ${(props: { color: string }) => props.color};
margin-right: 10px;
`;
function UsageBar ({ data }: PieChartProps): React.ReactElement<PieChartProps> {
const radius = 50;
const strokeWidth = 15;
const circumference = 2 * Math.PI * radius;
const total = data.reduce((acc, item) => acc + item.value, 0);
let cumulativeOffset = 0;
if (!total) {
return <></>;
}
return (
<Container>
<GraphContainer>
<svg
height={radius * 2}
viewBox={`0 0 ${radius * 2} ${radius * 2}`}
width={radius * 2}
>
<circle
cx={radius}
cy={radius}
fill='none'
r={radius - strokeWidth / 2}
stroke='#f0f0f0'
strokeWidth={strokeWidth}
/>
{data.map((item, index) => {
const percentage = (item.value / total) * 100;
const dashArray = (percentage / 100) * circumference;
const dashOffset = (cumulativeOffset / 100) * circumference;
cumulativeOffset += percentage;
return (
<circle
cx={radius}
cy={radius}
fill='none'
key={index}
r={radius - strokeWidth / 2}
stroke={item.color}
strokeDasharray={`${dashArray} ${circumference - dashArray}`}
strokeDashoffset={-dashOffset}
strokeWidth={strokeWidth}
transform={`rotate(-90 ${radius} ${radius})`}
>
<title>{`${item.label}: ${percentage.toFixed(2)}%`}</title>
</circle>
);
})}
</svg>
</GraphContainer>
<LegendContainer>
{data.map((item, index) => (
<LegendItem key={index}>
<ColorBox color={item.color} />
<span>{`${item.label}: ${((item.value / total) * 100).toFixed(2)}%`}</span>
</LegendItem>
))}
</LegendContainer>
</Container>
);
}
export default React.memo(UsageBar);

View File

@ -96,6 +96,7 @@ export { default as Toggle } from './Toggle.js';
export { default as ToggleGroup } from './ToggleGroup.js';
export { default as Tooltip } from './Tooltip.js';
export { default as TxButton } from './TxButton.js';
export { default as UsageBar } from './UsageBar.js';
export { default as VoteAccount } from './VoteAccount.js';
export { default as VoteValue } from './VoteValue.js';

View File

@ -18,7 +18,7 @@ const FACTORS = [0.2126, 0.7152, 0.0722];
const PARTS = [0, 2, 4];
const VERY_DARK = 16;
const defaultHighlight = '#f19135';
export const defaultHighlight = '#f19135';
function getHighlight (uiHighlight: string | undefined): string {
return (uiHighlight || defaultHighlight);

View File

@ -22,11 +22,16 @@ export { useBlockEvents } from './useBlockEvents.js';
export { useBlockInterval } from './useBlockInterval.js';
export { useBlocksPerDays } from './useBlocksPerDays.js';
export { useBlockTime } from './useBlockTime.js';
export { useBrokerLeases } from './useBrokerLeases.js';
export { useBrokerReservations } from './useBrokerReservations.js';
export { useBrokerStatus } from './useBrokerStatus.js';
export { useCacheKey } from './useCacheKey.js';
export { useCall } from './useCall.js';
export { useCallMulti } from './useCallMulti.js';
export { useCollectiveInstance } from './useCollectiveInstance.js';
export { useCollectiveMembers } from './useCollectiveMembers.js';
export { useCoreDescriptor } from './useCoreDescriptor.js';
export { useCurrentPrice } from './useCurrentPrice.js';
export { useDebounce } from './useDebounce.js';
export { useDelegations } from './useDelegations.js';
export { useDeriveAccountFlags } from './useDeriveAccountFlags.js';
@ -66,7 +71,10 @@ export { usePopupWindow } from './usePopupWindow.js';
export { usePreimage } from './usePreimage.js';
export { useProxies } from './useProxies.js';
export { useQueue } from './useQueue.js';
export { useQueueStatus } from './useQueueStatus.js';
export { useRegions } from './useRegions.js';
export { useRegistrars } from './useRegistrars.js';
export { useRenewalBump } from './useRenewalBump.js';
export { useSavedFlags } from './useSavedFlags.js';
export { useScroll } from './useScroll.js';
export { useStakingInfo } from './useStakingInfo.js';
@ -84,3 +92,5 @@ export { useVotingStatus } from './useVotingStatus.js';
export { useWeight } from './useWeight.js';
export { useWindowColumns } from './useWindowColumns.js';
export { useWindowSize } from './useWindowSize.js';
export { useWorkloadInfos } from './useWorkloadInfos.js';
export { useWorkplanInfos } from './useWorkplanInfos.js';

View File

@ -8,7 +8,7 @@ import type { DeriveAccountFlags, DeriveAccountRegistration } from '@polkadot/ap
import type { DisplayedJudgement } from '@polkadot/react-components/types';
import type { Option, u32, u128, Vec } from '@polkadot/types';
import type { AccountId, BlockNumber, Call, Hash, SessionIndex, ValidatorPrefs } from '@polkadot/types/interfaces';
import type { PalletPreimageRequestStatus, PalletStakingRewardDestination, PalletStakingStakingLedger, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';
import type { PalletPreimageRequestStatus, PalletStakingRewardDestination, PalletStakingStakingLedger, PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor, SpStakingExposurePage, SpStakingPagedExposureMetadata } from '@polkadot/types/lookup';
import type { ICompact, IExtrinsic, INumber } from '@polkadot/types/types';
import type { KeyringJson$Meta } from '@polkadot/ui-keyring/types';
import type { BN } from '@polkadot/util';
@ -19,7 +19,7 @@ export type CallParam = any;
export type CallParams = [] | CallParam[];
export interface CallOptions <T> {
export interface CallOptions<T> {
defaultValue?: T;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
paramMap?: (params: any) => CallParams;
@ -214,3 +214,61 @@ export interface WeightResult {
v1Weight: BN;
v2Weight: V2WeightConstruct;
}
export interface CoreDescription {
core: number;
info: PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor[];
}
export interface OnDemandQueueStatus {
traffic: u128;
nextIndex: u32;
smallestIndex: u32;
freedIndices: [string, u32][];
}
export interface CoreWorkload {
core: number,
info: CoreWorkloadInfo
}
export interface CoreWorkloadInfo {
task: number | string,
isTask: boolean
isPool: boolean
mask: string[]
maskBits: number
}
export interface CoreWorkplan {
core: number;
info: CoreWorkplanInfo
timeslice: number;
}
export interface CoreWorkplanInfo {
task: number | string,
isTask: boolean
isPool: boolean
mask: string[]
maskBits: number
}
export interface RegionInfo {
core: number,
start: number,
end: number,
owner: string,
paid: string,
mask: `0x${string}`
}
export interface Reservation {
task: string
mask: number,
}
export interface LegacyLease {
core: number,
until: number,
task: string
}

View File

@ -0,0 +1,34 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { Vec } from '@polkadot/types';
import type { PalletBrokerLeaseRecordItem } from '@polkadot/types/lookup';
import type { LegacyLease } from './types.js';
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 [state, setState] = useState<LegacyLease[]>();
useEffect((): void => {
if (!leases) {
return;
}
setState(
leases.map((info, index: number) => ({
core: index,
task: info.task.toString(),
until: info.until.toNumber()
})
));
}, [leases]);
return state;
}
export const useBrokerLeases = createNamedHook('useBrokerLeases', useBrokerLeasesImpl);

View File

@ -0,0 +1,37 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { Vec } from '@polkadot/types';
import type { PalletBrokerScheduleItem } from '@polkadot/types/lookup';
import type { Reservation } from './types.js';
import { useEffect, useState } from 'react';
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 [state, setState] = useState<Reservation[]>();
useEffect((): void => {
if (!reservations) {
return;
}
setState(
reservations.map((info: PalletBrokerScheduleItem[]) => {
return {
mask: processHexMask(info[0]?.mask)?.length ?? 0,
task: info[0]?.assignment?.isTask ? info[0]?.assignment?.asTask.toString() : info[0]?.assignment?.isPool ? 'Pool' : ''
};
}
));
}, [reservations]);
return state;
}
export const useBrokerReservations = createNamedHook('useBrokerReservations', useBrokerReservationsImpl);

View File

@ -0,0 +1,25 @@
// Copyright 2017-2024 @polkadot/react-query authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup';
import { useEffect, useState } from 'react';
import { createNamedHook, useApi, 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>();
useEffect((): void => {
status &&
setState(
status
);
}, [status]);
return state?.toJSON()[query]?.toString();
}
export const useBrokerStatus = createNamedHook('useBrokerStatus', useBrokerStatusImpl);

View File

@ -0,0 +1,51 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// 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 { useEffect, useState } from 'react';
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
function extractInfo (info: PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor[], core: number) {
return {
core,
info
};
}
const OPT_KEY = {
transform: (keys: StorageKey<[u32]>[]): u32[] =>
keys.map(({ args: [id] }) => id)
};
function useCoreDescriptorImpl (api: ApiPromise, ready: boolean): CoreDescription[] | undefined {
const keys = useMapKeys(ready && api.query.coretimeAssignmentProvider.coreDescriptors, [], OPT_KEY);
const sanitizedKeys = keys?.map((_, index) => {
return index;
});
sanitizedKeys?.pop();
const coreDescriptors = useCall<[[number[]], Vec<PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor>[]]>(ready && api.query.coretimeAssignmentProvider.coreDescriptors.multi, [sanitizedKeys], { withParams: true });
const [state, setState] = useState<CoreDescription[] | undefined>();
useEffect((): void => {
coreDescriptors &&
setState(
coreDescriptors[0][0].map((info, index) => {
return extractInfo(coreDescriptors[1][index], info);
}
)
);
}, [coreDescriptors]);
return state;
}
export const useCoreDescriptor = createNamedHook('useCoreDescriptor', useCoreDescriptorImpl);

View File

@ -0,0 +1,31 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerSaleInfoRecord } from '@polkadot/types/lookup';
import { useEffect, useState } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function extractCurrentPrice (saleInfo: PalletBrokerSaleInfoRecord) {
return saleInfo.toJSON().price?.toString();
}
function useCurrentPriceImpl () {
const { api, isApiReady } = useApi();
const saleInfo = useCall<PalletBrokerSaleInfoRecord>(isApiReady && api.query.broker.saleInfo);
const [state, setState] = useState<string | undefined>();
useEffect((): void => {
saleInfo &&
setState(
extractCurrentPrice(saleInfo)
);
}, [saleInfo]);
return state;
}
export const useCurrentPrice = createNamedHook('useCurrentPrice', useCurrentPriceImpl);

View File

@ -0,0 +1,36 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { OnDemandQueueStatus } from './types.js';
import { useEffect, useState } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function extractInfo (value: OnDemandQueueStatus) {
return {
freedIndices: value.freedIndices,
nextIndex: value.nextIndex,
smallestIndex: value.smallestIndex,
traffic: value.traffic
};
}
function useQueueStatusImpl (): OnDemandQueueStatus | undefined {
const { api } = useApi();
const queue = useCall<OnDemandQueueStatus>(api.query.onDemandAssignmentProvider.queueStatus);
const [state, setState] = useState<OnDemandQueueStatus | undefined>();
useEffect((): void => {
queue &&
setState(
extractInfo(queue)
);
}, [queue]);
return state;
}
export const useQueueStatus = createNamedHook('useQueueStatus', useQueueStatusImpl);

View File

@ -0,0 +1,49 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { Option, StorageKey } from '@polkadot/types';
import type { PalletBrokerRegionId, PalletBrokerRegionRecord } from '@polkadot/types/lookup';
import type { RegionInfo } from './types.js';
import { useEffect, useState } from 'react';
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
function extractInfo (core: number, start: number, end: number, owner: string, paid: string, mask: `0x${string}`) {
return {
core,
end,
mask,
owner,
paid,
start
};
}
const OPT_KEY = {
transform: (keys: StorageKey<[PalletBrokerRegionId]>[]): PalletBrokerRegionId[] =>
keys.map(({ args: [regionId] }) => regionId)
};
function useRegionsImpl (api: ApiPromise): RegionInfo[] | undefined {
const regionKeys = useMapKeys(api.query.broker.regions, [], OPT_KEY);
const regionInfo = useCall<[[PalletBrokerRegionId[]], Option<PalletBrokerRegionRecord>[]]>(api.query.broker.regions.multi, [regionKeys], { withParams: true });
const [state, setState] = useState<RegionInfo[] | undefined>();
useEffect((): void => {
regionInfo &&
regionInfo[0][0].length > 0 &&
setState(
regionInfo[0][0].map((info, index) =>
extractInfo(info.core.toNumber(), info.begin.toNumber(), regionInfo[1][index].unwrap().end.toNumber(), regionInfo[1][index].unwrap().owner.toString(), regionInfo[1][index].unwrap().paid.toString(), info.mask.toHex())
)
);
}, [regionInfo]);
return state;
}
export const useRegions = createNamedHook('useRegions', useRegionsImpl);

View File

@ -0,0 +1,31 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerConfigRecord } from '@polkadot/types/lookup';
import { useEffect, useState } from 'react';
import { createNamedHook, useApi, useCall } from '@polkadot/react-hooks';
function extractRenewalBump (config: PalletBrokerConfigRecord) {
return config.toJSON().renewalBump?.toString();
}
function useRenewalBumpImpl () {
const { api, isApiReady } = useApi();
const config = useCall<PalletBrokerConfigRecord>(isApiReady && api.query.broker.configuration);
const [state, setState] = useState<string | undefined>();
useEffect((): void => {
config &&
setState(
extractRenewalBump(config)
);
}, [config]);
return state;
}
export const useRenewalBump = createNamedHook('useRenewalBump', useRenewalBumpImpl);

View File

@ -0,0 +1,67 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { StorageKey, u32, Vec } from '@polkadot/types';
import type { PalletBrokerScheduleItem } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { CoreWorkload } from './types.js';
import { useEffect, useState } from 'react';
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
import { processHexMask } from './utils/dataProcessing.js';
export function sortByCore<T extends { core: number }> (dataArray?: T | T[]): T[] {
if (!dataArray) {
return [];
}
const sanitized = Array.isArray(dataArray) ? dataArray : [dataArray];
return sanitized.sort((a, b) => a.core - b.core);
}
function extractInfo (info: PalletBrokerScheduleItem[], core: number): CoreWorkload {
const mask: string[] = processHexMask(info[0]?.mask);
const assignment = info[0].assignment;
return {
core,
info: {
isPool: assignment.isPool,
isTask: assignment.isTask,
mask,
maskBits: mask.length,
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : ''
}
};
}
const OPT_KEY = {
transform: (keys: StorageKey<[u32]>[]): u32[] =>
keys.map(({ args: [core] }) => core)
};
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 [state, setState] = useState<CoreWorkload[] | undefined>();
useEffect((): void => {
if (!workloadInfo?.[0]?.[0]) {
return;
}
const cores = workloadInfo[0][0];
setState(
sortByCore(cores.map((core, index) => extractInfo(workloadInfo[1][index], core.toNumber())))
);
}, [workloadInfo]);
return state;
}
export const useWorkloadInfos = createNamedHook('useWorkloadInfos', useWorkloadInfosImpl);

View File

@ -0,0 +1,76 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { ApiPromise } from '@polkadot/api';
import type { Option, StorageKey, u16, u32, Vec } from '@polkadot/types';
import type { PalletBrokerScheduleItem } from '@polkadot/types/lookup';
import type { BN } from '@polkadot/util';
import type { CoreWorkplan } from './types.js';
import { useEffect, useState } from 'react';
import { createNamedHook, useCall, useMapKeys } from '@polkadot/react-hooks';
import { processHexMask } from './utils/dataProcessing.js';
export function sortByCore<T extends { core: number }> (dataArray?: T | T[]): T[] {
if (!dataArray) {
return [];
}
const sanitized = Array.isArray(dataArray) ? dataArray : [dataArray];
return sanitized.sort((a, b) => a.core - b.core);
}
function extractInfo (info: Vec<PalletBrokerScheduleItem>, core: number, timeslice: number): CoreWorkplan {
const mask: string[] = processHexMask(info[0]?.mask);
const assignment = info[0].assignment;
return {
core,
info: {
isPool: assignment.isPool,
isTask: assignment.isTask,
mask,
maskBits: mask.length,
task: assignment.isTask ? assignment.asTask.toString() : assignment.isPool ? 'Pool' : ''
},
timeslice
};
}
const OPT_KEY = {
transform: (keys: StorageKey<[u32, u16]>[]): [u32, u16][] =>
keys.map(({ args: [timeslice, core] }) => [timeslice, core])
};
function useWorkplanInfosImpl (api: ApiPromise, ready: boolean): CoreWorkplan[] | undefined {
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 [state, setState] = useState<CoreWorkplan[] | undefined>();
useEffect(() => {
if (!workplanInfo?.[1] || !workplanInfo[0]?.[0]) {
return;
}
const coreInfo = workplanInfo[0][0];
setState(
sortByCore(coreInfo.map((core: BN[], index) =>
extractInfo(workplanInfo[1][index].unwrap(), core[1].toNumber(), core[0].toNumber())
))
);
}, [workplanInfo]);
return state;
}
export const useWorkplanInfos = createNamedHook('useWorkplanInfos', useWorkplanInfosImpl);

View File

@ -0,0 +1,25 @@
// Copyright 2017-2024 @polkadot/react-hooks authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerScheduleItem } from '@polkadot/types/lookup';
export function hexToBin (hex: string): string {
return parseInt(hex, 16).toString(2);
}
export function processHexMask (mask: PalletBrokerScheduleItem['mask'] | undefined): string[] {
if (!mask) {
return [];
}
const trimmedHex: string = mask.toHex().slice(2);
const arr: string[] = trimmedHex.split('');
const buffArr: string[] = [];
arr.forEach((bit) => {
hexToBin(bit).split('').forEach((v) => buffArr.push(v));
});
buffArr.filter((v) => v === '1');
return buffArr;
}

View File

@ -0,0 +1,25 @@
// 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);

View File

@ -0,0 +1,29 @@
// Copyright 2017-2024 @polkadot/react-query authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor } from '@polkadot/types/lookup';
import React from 'react';
import { useApi, useCall } from '@polkadot/react-hooks';
interface Props {
children?: React.ReactNode;
className?: string;
query: string;
}
function BrokerStatus ({ children, className = '', query }: Props): React.ReactElement<Props> {
const { api } = useApi();
const status = useCall<PolkadotRuntimeParachainsAssignerCoretimeCoreDescriptor>(api.query.broker?.status);
const strStatus = status === undefined ? '' : status.toJSON()[query];
return (
<div className={className}>
{strStatus?.toString()}
{children}
</div>
);
}
export default React.memo(BrokerStatus);

View File

@ -21,6 +21,7 @@ interface Props {
isShort?: boolean;
label?: React.ReactNode;
labelPost?: LabelPost;
useTicker?: boolean;
value?: Compact<any> | BN | string | number | null;
valueFormatted?: string;
withCurrency?: boolean;
@ -47,8 +48,12 @@ function getFormat (registry: Registry, formatIndex = 0): [number, string] {
];
}
function createElement (prefix: string, postfix: string, unit: string, label: LabelPost = '', isShort = false): React.ReactNode {
return <>{`${prefix}${isShort ? '' : '.'}`}{!isShort && <span className='ui--FormatBalance-postfix'>{`0000${postfix || ''}`.slice(-4)}</span>}<span className='ui--FormatBalance-unit'> {unit}</span>{label}</>;
function createElement (prefix: string, postfix: string, unit: string, label: LabelPost = '', isShort = false, ticker?: string): React.ReactNode {
if (ticker) {
return <>{`${prefix}${isShort ? '' : '.'}`}{!isShort && <span className='ui--FormatBalance-postfix'>{`${postfix || ''}`.slice(-4)}</span>}<span className='ui--FormatBalance-unit'> {ticker}</span>{label}</>;
} else {
return <>{`${prefix}${isShort ? '' : '.'}`}{!isShort && <span className='ui--FormatBalance-postfix'>{`0000${postfix || ''}`.slice(-4)}</span>}<span className='ui--FormatBalance-unit'> {unit}</span>{label}</>;
}
}
function splitFormat (value: string, label?: LabelPost, isShort?: boolean): React.ReactNode {
@ -58,8 +63,8 @@ function splitFormat (value: string, label?: LabelPost, isShort?: boolean): Reac
return createElement(prefix, postfix, unit, label, isShort);
}
function applyFormat (value: Compact<any> | BN | string | number, [decimals, token]: [number, string], withCurrency = true, withSi?: boolean, _isShort?: boolean, labelPost?: LabelPost): React.ReactNode {
const [prefix, postfix] = formatBalance(value, { decimals, forceUnit: '-', withSi: false }).split('.');
function applyFormat (value: Compact<any> | BN | string | number, [decimals, token]: [number, string], withCurrency = true, withSi?: boolean, _isShort?: boolean, labelPost?: LabelPost, useTicker?: boolean): React.ReactNode {
const [prefix, postfix, ticker] = formatBalance(value, { decimals }).split(/[.\s]+/);
const isShort = _isShort || (withSi && prefix.length >= K_LENGTH);
const unitPost = withCurrency ? token : '';
@ -71,10 +76,14 @@ function applyFormat (value: Compact<any> | BN | string | number, [decimals, tok
return <>{major}.<span className='ui--FormatBalance-postfix'>{minor}</span><span className='ui--FormatBalance-unit'>{unit}{unit ? unitPost : ` ${unitPost}`}</span>{labelPost || ''}</>;
}
return createElement(prefix, postfix, unitPost, labelPost, isShort);
if (useTicker) {
return createElement(prefix, postfix, unitPost, labelPost, isShort, ticker);
} else {
return createElement(prefix, postfix, unitPost, labelPost, isShort);
}
}
function FormatBalance ({ children, className = '', format, formatIndex, isShort, label, labelPost, value, valueFormatted, withCurrency, withSi }: Props): React.ReactElement<Props> {
function FormatBalance ({ children, className = '', format, formatIndex, isShort, label, labelPost, useTicker, value, valueFormatted, withCurrency, withSi }: Props): React.ReactElement<Props> {
const { t } = useTranslation();
const { api } = useApi();
@ -96,7 +105,7 @@ function FormatBalance ({ children, className = '', format, formatIndex, isShort
: value
? value === 'all'
? <>{t('everything')}{labelPost || ''}</>
: applyFormat(value, formatInfo, withCurrency, withSi, isShort, labelPost)
: applyFormat(value, formatInfo, withCurrency, withSi, isShort, labelPost, useTicker)
: isString(labelPost)
? `-${labelPost.toString()}`
: labelPost
@ -122,7 +131,6 @@ const StyledSpan = styled.span`
.ui--FormatBalance-unit {
font-size: var(--font-percent-tiny);
text-transform: uppercase;
}
.ui--FormatBalance-value {

View File

@ -0,0 +1,38 @@
// Copyright 2017-2024 @polkadot/react-query authors & contributors
// SPDX-License-Identifier: Apache-2.0
import type { PalletBrokerStatusRecord } from '@polkadot/types/lookup';
import React from 'react';
import { useApi, useCall } from '@polkadot/react-hooks';
interface Props {
children?: React.ReactNode;
className?: string;
}
function PoolSize ({ children, className = '' }: Props): React.ReactElement<Props> {
const { api } = useApi();
const status = useCall<PalletBrokerStatusRecord>(api.query.broker?.status);
let systemPool = 0;
let privatePool = 0;
let poolSize = '';
if (status === undefined) {
poolSize = '0';
} else {
systemPool = status.toJSON().systemPoolSize as number;
privatePool = status.toJSON().systemPoolSize as number;
poolSize = (systemPool + privatePool).toString();
}
return (
<div className={className}>
{poolSize}
{children}
</div>
);
}
export default React.memo(PoolSize);

View File

@ -8,6 +8,7 @@ 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';
@ -15,6 +16,7 @@ export { default as LockedVote } from './LockedVote.js';
export { default as NodeName } from './NodeName.js';
export { default as NodeVersion } from './NodeVersion.js';
export { default as Nonce } from './Nonce.js';
export { default as PoolSize } from './PoolSize.js';
export { default as SessionToTime } from './SessionToTime.js';
export { default as TimeNow } from './TimeNow.js';
export { default as TotalInactive } from './TotalInactive.js';

View File

@ -25,6 +25,7 @@
"@polkadot/app-alliance": ["page-alliance/src/index.tsx"],
"@polkadot/app-ambassador": ["page-ambassador/src/index.tsx"],
"@polkadot/app-assets": ["page-assets/src/index.tsx"],
"@polkadot/app-broker": ["page-broker/src/index.tsx"],
"@polkadot/app-bounties": ["page-bounties/src/index.tsx"],
"@polkadot/app-calendar": ["page-calendar/src/index.tsx"],
"@polkadot/app-claims": ["page-claims/src/index.tsx"],

View File

@ -21,6 +21,7 @@
{ "path": "./packages/page-bounties/tsconfig.build.json" },
{ "path": "./packages/page-bounties/tsconfig.spec.json" },
{ "path": "./packages/page-bounties/tsconfig.test.json" },
{ "path": "./packages/page-broker/tsconfig.build.json" },
{ "path": "./packages/page-calendar/tsconfig.build.json" },
{ "path": "./packages/page-claims/tsconfig.build.json" },
{ "path": "./packages/page-claims/tsconfig.spec.json" },

View File

@ -1343,6 +1343,19 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/app-broker@workspace:packages/page-broker":
version: 0.0.0-use.local
resolution: "@polkadot/app-broker@workspace:packages/page-broker"
dependencies:
"@polkadot/react-components": "npm:0.143.3-21-x"
"@polkadot/react-query": "npm:0.143.3-21-x"
peerDependencies:
react: "*"
react-dom: "*"
react-is: "*"
languageName: unknown
linkType: soft
"@polkadot/app-calendar@workspace:packages/page-calendar":
version: 0.0.0-use.local
resolution: "@polkadot/app-calendar@workspace:packages/page-calendar"
@ -2122,7 +2135,7 @@ __metadata:
languageName: unknown
linkType: soft
"@polkadot/react-components@npm:^0.143.3-21-x, @polkadot/react-components@workspace:packages/react-components":
"@polkadot/react-components@npm:0.143.3-21-x, @polkadot/react-components@npm:^0.143.3-21-x, @polkadot/react-components@workspace:packages/react-components":
version: 0.0.0-use.local
resolution: "@polkadot/react-components@workspace:packages/react-components"
dependencies:
@ -2246,7 +2259,7 @@ __metadata:
languageName: node
linkType: hard
"@polkadot/react-query@npm:^0.143.3-21-x, @polkadot/react-query@workspace:packages/react-query":
"@polkadot/react-query@npm:0.143.3-21-x, @polkadot/react-query@npm:^0.143.3-21-x, @polkadot/react-query@workspace:packages/react-query":
version: 0.0.0-use.local
resolution: "@polkadot/react-query@workspace:packages/react-query"
peerDependencies:
@ -2905,7 +2918,14 @@ __metadata:
languageName: node
linkType: hard
"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.5, @scure/base@npm:^1.1.7, @scure/base@npm:~1.1.0":
"@scure/base@npm:^1.1.1, @scure/base@npm:^1.1.5, @scure/base@npm:~1.1.0":
version: 1.1.5
resolution: "@scure/base@npm:1.1.5"
checksum: 10/543fa9991c6378b6a0d5ab7f1e27b30bb9c1e860d3ac81119b4213cfdf0ad7b61be004e06506e89de7ce0cec9391c17f5c082bb34c3b617a2ee6a04129f52481
languageName: node
linkType: hard
"@scure/base@npm:^1.1.7":
version: 1.1.7
resolution: "@scure/base@npm:1.1.7"
checksum: 10/fc50ffaab36cb46ff9fa4dc5052a06089ab6a6707f63d596bb34aaaec76173c9a564ac312a0b981b5e7a5349d60097b8878673c75d6cbfc4da7012b63a82099b
@ -3304,7 +3324,14 @@ __metadata:
languageName: node
linkType: hard
"@types/estree@npm:*, @types/estree@npm:1.0.5, @types/estree@npm:^1.0.0":
"@types/estree@npm:*, @types/estree@npm:^1.0.0":
version: 1.0.0
resolution: "@types/estree@npm:1.0.0"
checksum: 10/9ec366ea3b94db26a45262d7161456c9ee25fd04f3a0da482f6e97dbf90c0c8603053c311391a877027cc4ee648340f988cd04f11287886cdf8bc23366291ef9
languageName: node
linkType: hard
"@types/estree@npm:1.0.5":
version: 1.0.5
resolution: "@types/estree@npm:1.0.5"
checksum: 10/7de6d928dd4010b0e20c6919e1a6c27b61f8d4567befa89252055fad503d587ecb9a1e3eab1b1901f923964d7019796db810b7fd6430acb26c32866d126fd408
@ -10208,11 +10235,11 @@ __metadata:
linkType: hard
"i18next@npm:*, i18next@npm:^23.7.11":
version: 23.12.2
resolution: "i18next@npm:23.12.2"
version: 23.7.11
resolution: "i18next@npm:23.7.11"
dependencies:
"@babel/runtime": "npm:^7.23.2"
checksum: 10/d7a743c54b83acc1203315e547bfe830bfe825dddd7706646aec2a49cb74254bcda70645b568d1bed55ee3610ba5e6f6012fb3c13f03080c1dd0f99db2c45478
checksum: 10/1127bc17f94459d40bd9aaa0350e9786d3853eb82449aabb4514e187fafc752c76a3f52c6be1c2722bfdadaa74f0d26b4f7dd04528ba6b2de7e34f5c6c019c21
languageName: node
linkType: hard