mirror of
https://github.com/pezkuwichain/pezkuwi-apps.git
synced 2026-07-26 22:45:52 +00:00
feat: initial Pezkuwi Apps rebrand from polkadot-apps
Rebranded terminology: - Polkadot → Pezkuwi - Kusama → Dicle - Westend → Zagros - Rococo → PezkuwiChain - Substrate → Bizinikiwi - parachain → teyrchain Custom logos with Kurdistan brand colors (#e6007a → #86e62a): - bizinikiwi-hexagon.svg - sora-bizinikiwi.svg - hezscanner.svg - heztreasury.svg - pezkuwiscan.svg - pezkuwistats.svg - pezkuwiassembly.svg - pezkuwiholic.svg
This commit is contained in:
@@ -0,0 +1,77 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-explorer authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { PeerInfo } from '@pezkuwi/types/interfaces';
|
||||
|
||||
import React, { useMemo, useRef } from 'react';
|
||||
|
||||
import { styled, Table } from '@pezkuwi/react-components';
|
||||
import { formatNumber, stringPascalCase } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
className?: string;
|
||||
peers?: PeerInfo[] | null;
|
||||
}
|
||||
|
||||
function sortPeers (peers: PeerInfo[]) {
|
||||
return peers
|
||||
.map(({ bestHash, bestNumber, peerId, roles }) => ({
|
||||
bestHash: bestHash.toHex(),
|
||||
bestNumber,
|
||||
peerId: peerId.toString(),
|
||||
roles: stringPascalCase(roles)
|
||||
}))
|
||||
.sort((a, b) => a.peerId.localeCompare(b.peerId))
|
||||
.sort((a, b) => a.roles.localeCompare(b.roles))
|
||||
.sort((a, b) => b.bestNumber.cmp(a.bestNumber));
|
||||
}
|
||||
|
||||
function Peers ({ className = '', peers }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const headerRef = useRef<([React.ReactNode?, string?, number?] | false)[]>([
|
||||
[t('connected peers'), 'start', 2],
|
||||
[t('best hash'), 'start'],
|
||||
[t('best #'), 'number']
|
||||
]);
|
||||
|
||||
const sorted = useMemo(
|
||||
() => peers && sortPeers(peers),
|
||||
[peers]
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTable
|
||||
className={className}
|
||||
empty={t('no peers connected')}
|
||||
header={headerRef.current}
|
||||
>
|
||||
{sorted?.map(({ bestHash, bestNumber, peerId, roles }) => (
|
||||
<tr key={peerId}>
|
||||
<td className='roles'>{roles}</td>
|
||||
<td className='hash overflow'>{peerId}</td>
|
||||
<td className='hash overflow'>{bestHash}</td>
|
||||
<td className='number bestNumber'>{formatNumber(bestNumber)}</td>
|
||||
</tr>
|
||||
))}
|
||||
</StyledTable>
|
||||
);
|
||||
}
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
overflow-x: auto;
|
||||
|
||||
td.roles {
|
||||
max-width: 9ch;
|
||||
width: 9ch;
|
||||
}
|
||||
|
||||
td.bestNumber {
|
||||
max-width: 11ch;
|
||||
width: 11ch;
|
||||
}
|
||||
`;
|
||||
|
||||
export default React.memo(Peers);
|
||||
@@ -0,0 +1,84 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-explorer authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Info } from './types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { CardSummary, SummaryBox } from '@pezkuwi/react-components';
|
||||
import { BestNumber, Elapsed } from '@pezkuwi/react-query';
|
||||
import { BN_ZERO, formatNumber } from '@pezkuwi/util';
|
||||
|
||||
import { useTranslation } from '../translate.js';
|
||||
|
||||
interface Props {
|
||||
nextRefresh: number;
|
||||
info: Info;
|
||||
}
|
||||
|
||||
const EMPTY_INFO = { extrinsics: null, health: null, peers: null };
|
||||
|
||||
function Summary ({ info: { extrinsics, health, peers } = EMPTY_INFO, nextRefresh }: Props): React.ReactElement<Props> {
|
||||
const { t } = useTranslation();
|
||||
const [peerBest, setPeerBest] = useState(BN_ZERO);
|
||||
|
||||
useEffect((): void => {
|
||||
if (peers) {
|
||||
const bestPeer = peers.sort((a, b): number => b.bestNumber.cmp(a.bestNumber))[0];
|
||||
|
||||
setPeerBest(
|
||||
bestPeer
|
||||
? bestPeer.bestNumber
|
||||
: BN_ZERO
|
||||
);
|
||||
}
|
||||
}, [peers]);
|
||||
|
||||
return (
|
||||
<SummaryBox>
|
||||
<section>
|
||||
<CardSummary label={t('refresh in')}>
|
||||
<Elapsed value={nextRefresh} />
|
||||
</CardSummary>
|
||||
{health && (
|
||||
<>
|
||||
<CardSummary
|
||||
className='media--800'
|
||||
label={t('total peers')}
|
||||
>
|
||||
{formatNumber(health.peers)}
|
||||
</CardSummary>
|
||||
<CardSummary
|
||||
className='media--800'
|
||||
label={t('syncing')}
|
||||
>
|
||||
{health.isSyncing.valueOf()
|
||||
? t('yes')
|
||||
: t('no')
|
||||
}
|
||||
</CardSummary>
|
||||
</>
|
||||
)}
|
||||
</section>
|
||||
{extrinsics && (extrinsics.length > 0) && (
|
||||
<section className='media--1200'>
|
||||
<CardSummary label={t('queued tx')}>
|
||||
{extrinsics.length}
|
||||
</CardSummary>
|
||||
</section>
|
||||
)}
|
||||
<section>
|
||||
{peerBest?.gtn(0) && (
|
||||
<CardSummary label={t('peer best')}>
|
||||
{formatNumber(peerBest)}
|
||||
</CardSummary>
|
||||
)}
|
||||
<CardSummary label={t('our best')}>
|
||||
<BestNumber />
|
||||
</CardSummary>
|
||||
</section>
|
||||
</SummaryBox>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(Summary);
|
||||
@@ -0,0 +1,72 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-explorer authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { ApiPromise } from '@pezkuwi/api';
|
||||
import type { Info } from './types.js';
|
||||
|
||||
import React, { useEffect, useState } from 'react';
|
||||
|
||||
import { useApi } from '@pezkuwi/react-hooks';
|
||||
|
||||
import Extrinsics from '../BlockInfo/Extrinsics.js';
|
||||
import { useTranslation } from '../translate.js';
|
||||
import Peers from './Peers.js';
|
||||
import Summary from './Summary.js';
|
||||
|
||||
const POLL_TIMEOUT = 9900;
|
||||
|
||||
async function retrieveInfo (api: ApiPromise): Promise<Partial<Info>> {
|
||||
try {
|
||||
const [blockNumber, health, peers, extrinsics] = await Promise.all([
|
||||
api.derive.chain.bestNumber(),
|
||||
api.rpc.system.health().catch(() => null),
|
||||
api.rpc.system.peers().catch(() => null),
|
||||
api.rpc.author.pendingExtrinsics().catch(() => null)
|
||||
]);
|
||||
|
||||
return { blockNumber, extrinsics, health, peers };
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
function NodeInfo (): React.ReactElement {
|
||||
const { t } = useTranslation();
|
||||
const { api } = useApi();
|
||||
const [info, setInfo] = useState<Partial<Info>>({});
|
||||
const [nextRefresh, setNextRefresh] = useState(() => Date.now());
|
||||
|
||||
useEffect((): () => void => {
|
||||
const getStatus = (): void => {
|
||||
setNextRefresh(Date.now() + POLL_TIMEOUT);
|
||||
retrieveInfo(api).then(setInfo).catch(console.error);
|
||||
};
|
||||
|
||||
getStatus();
|
||||
|
||||
const timerId = window.setInterval(getStatus, POLL_TIMEOUT);
|
||||
|
||||
return (): void => {
|
||||
window.clearInterval(timerId);
|
||||
};
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Summary
|
||||
info={info}
|
||||
nextRefresh={nextRefresh}
|
||||
/>
|
||||
<Peers peers={info.peers} />
|
||||
<Extrinsics
|
||||
blockNumber={info.blockNumber}
|
||||
label={t('pending extrinsics')}
|
||||
value={info.extrinsics}
|
||||
withLink
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export default React.memo(NodeInfo);
|
||||
@@ -0,0 +1,12 @@
|
||||
// Copyright 2017-2025 @pezkuwi/app-explorer authors & contributors
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
import type { Vec } from '@pezkuwi/types';
|
||||
import type { BlockNumber, Extrinsic, Health, PeerInfo } from '@pezkuwi/types/interfaces';
|
||||
|
||||
export interface Info {
|
||||
blockNumber?: BlockNumber;
|
||||
extrinsics?: Vec<Extrinsic> | null;
|
||||
health?: Health | null;
|
||||
peers?: PeerInfo[] | null;
|
||||
}
|
||||
Reference in New Issue
Block a user