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:
parent
20a8252411
commit
7c9f388698
@ -957,6 +957,7 @@ export const prodParasKusamaCommon: EndpointOption[] = [
|
||||
teleport: [-1],
|
||||
text: 'Coretime',
|
||||
ui: {
|
||||
color: '#113911',
|
||||
logo: chainsCoretimeKusamaSVG
|
||||
}
|
||||
},
|
||||
|
||||
@ -720,7 +720,9 @@ export const testParasRococoCommon: EndpointOption[] = [
|
||||
relayName: 'rococo',
|
||||
teleport: [-1],
|
||||
text: 'Coretime',
|
||||
ui: {}
|
||||
ui: {
|
||||
color: '#f19135'
|
||||
}
|
||||
},
|
||||
{
|
||||
homepage: 'https://encointer.org/',
|
||||
|
||||
@ -182,7 +182,9 @@ export const testParasWestendCommon: EndpointOption[] = [
|
||||
relayName: 'westend',
|
||||
teleport: [-1],
|
||||
text: 'Coretime',
|
||||
ui: {}
|
||||
ui: {
|
||||
color: '#f19135'
|
||||
}
|
||||
},
|
||||
{
|
||||
info: 'westendPeople',
|
||||
|
||||
22
packages/apps-routing/src/broker.ts
Normal file
22
packages/apps-routing/src/broker.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-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' })
|
||||
};
|
||||
}
|
||||
@ -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),
|
||||
|
||||
@ -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" },
|
||||
|
||||
31
packages/apps/public/locales/en/app-broker.json
Normal file
31
packages/apps/public/locales/en/app-broker.json
Normal 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"
|
||||
}
|
||||
@ -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",
|
||||
|
||||
@ -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": "",
|
||||
|
||||
@ -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",
|
||||
|
||||
0
packages/page-broker/.skip-build
Normal file
0
packages/page-broker/.skip-build
Normal file
0
packages/page-broker/.skip-npm
Normal file
0
packages/page-broker/.skip-npm
Normal file
1
packages/page-broker/README.md
Normal file
1
packages/page-broker/README.md
Normal file
@ -0,0 +1 @@
|
||||
# @polkadot/app-broker
|
||||
27
packages/page-broker/package.json
Normal file
27
packages/page-broker/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-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": "*"
|
||||
}
|
||||
}
|
||||
54
packages/page-broker/src/Overview/CoreTable.tsx
Normal file
54
packages/page-broker/src/Overview/CoreTable.tsx
Normal 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);
|
||||
47
packages/page-broker/src/Overview/CoresTables.tsx
Normal file
47
packages/page-broker/src/Overview/CoresTables.tsx
Normal 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);
|
||||
105
packages/page-broker/src/Overview/Filters.tsx
Normal file
105
packages/page-broker/src/Overview/Filters.tsx
Normal 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);
|
||||
93
packages/page-broker/src/Overview/Summary.tsx
Normal file
93
packages/page-broker/src/Overview/Summary.tsx
Normal 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);
|
||||
24
packages/page-broker/src/Overview/Summary/Cores.tsx
Normal file
24
packages/page-broker/src/Overview/Summary/Cores.tsx
Normal 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);
|
||||
28
packages/page-broker/src/Overview/Summary/RegionLength.tsx
Normal file
28
packages/page-broker/src/Overview/Summary/RegionLength.tsx
Normal 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);
|
||||
30
packages/page-broker/src/Overview/Summary/RenewalPrice.tsx
Normal file
30
packages/page-broker/src/Overview/Summary/RenewalPrice.tsx
Normal 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);
|
||||
24
packages/page-broker/src/Overview/Summary/Timeslice.tsx
Normal file
24
packages/page-broker/src/Overview/Summary/Timeslice.tsx
Normal 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);
|
||||
@ -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);
|
||||
141
packages/page-broker/src/Overview/WorkInfoRow.tsx
Normal file
141
packages/page-broker/src/Overview/WorkInfoRow.tsx
Normal 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 || <> </>}</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> </p>}
|
||||
</StyledTableCol></>;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default React.memo(WorkInfoRow);
|
||||
85
packages/page-broker/src/Overview/Workload.tsx
Normal file
85
packages/page-broker/src/Overview/Workload.tsx
Normal 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);
|
||||
56
packages/page-broker/src/Overview/Workplan.tsx
Normal file
56
packages/page-broker/src/Overview/Workplan.tsx
Normal 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);
|
||||
95
packages/page-broker/src/Overview/index.tsx
Normal file
95
packages/page-broker/src/Overview/index.tsx
Normal 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);
|
||||
49
packages/page-broker/src/index.tsx
Normal file
49
packages/page-broker/src/index.tsx
Normal 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);
|
||||
8
packages/page-broker/src/translate.ts
Normal file
8
packages/page-broker/src/translate.ts
Normal 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');
|
||||
}
|
||||
45
packages/page-broker/src/types.ts
Normal file
45
packages/page-broker/src/types.ts
Normal 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
|
||||
}
|
||||
115
packages/page-broker/src/utils.ts
Normal file
115
packages/page-broker/src/utils.ts
Normal 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;
|
||||
};
|
||||
13
packages/page-broker/tsconfig.build.json
Normal file
13
packages/page-broker/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" }
|
||||
]
|
||||
}
|
||||
107
packages/react-components/src/UsageBar.tsx
Normal file
107
packages/react-components/src/UsageBar.tsx
Normal 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);
|
||||
@ -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';
|
||||
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
34
packages/react-hooks/src/useBrokerLeases.ts
Normal file
34
packages/react-hooks/src/useBrokerLeases.ts
Normal 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);
|
||||
37
packages/react-hooks/src/useBrokerReservations.ts
Normal file
37
packages/react-hooks/src/useBrokerReservations.ts
Normal 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);
|
||||
25
packages/react-hooks/src/useBrokerStatus.ts
Normal file
25
packages/react-hooks/src/useBrokerStatus.ts
Normal 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);
|
||||
51
packages/react-hooks/src/useCoreDescriptor.ts
Normal file
51
packages/react-hooks/src/useCoreDescriptor.ts
Normal 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);
|
||||
31
packages/react-hooks/src/useCurrentPrice.tsx
Normal file
31
packages/react-hooks/src/useCurrentPrice.tsx
Normal 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);
|
||||
36
packages/react-hooks/src/useQueueStatus.ts
Normal file
36
packages/react-hooks/src/useQueueStatus.ts
Normal 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);
|
||||
49
packages/react-hooks/src/useRegions.ts
Normal file
49
packages/react-hooks/src/useRegions.ts
Normal 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);
|
||||
31
packages/react-hooks/src/useRenewalBump.ts
Normal file
31
packages/react-hooks/src/useRenewalBump.ts
Normal 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);
|
||||
67
packages/react-hooks/src/useWorkloadInfos.ts
Normal file
67
packages/react-hooks/src/useWorkloadInfos.ts
Normal 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);
|
||||
76
packages/react-hooks/src/useWorkplanInfos.ts
Normal file
76
packages/react-hooks/src/useWorkplanInfos.ts
Normal 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);
|
||||
25
packages/react-hooks/src/utils/dataProcessing.ts
Normal file
25
packages/react-hooks/src/utils/dataProcessing.ts
Normal 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;
|
||||
}
|
||||
25
packages/react-query/src/BrokerStatus.tsx
Normal file
25
packages/react-query/src/BrokerStatus.tsx
Normal 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);
|
||||
29
packages/react-query/src/CoreDescriptor.tsx
Normal file
29
packages/react-query/src/CoreDescriptor.tsx
Normal 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);
|
||||
@ -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 {
|
||||
|
||||
38
packages/react-query/src/PoolSize.tsx
Normal file
38
packages/react-query/src/PoolSize.tsx
Normal 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);
|
||||
@ -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';
|
||||
|
||||
@ -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"],
|
||||
|
||||
@ -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" },
|
||||
|
||||
41
yarn.lock
41
yarn.lock
@ -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
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user