Skip to content

Commit

Permalink
feat: uptime & penalty section
Browse files Browse the repository at this point in the history
  • Loading branch information
evilpeach committed Mar 19, 2024
1 parent 3544d61 commit f65c634
Show file tree
Hide file tree
Showing 13 changed files with 216 additions and 106 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Flex, Text } from "@chakra-ui/react";

import { ExplorerLink } from "lib/components/ExplorerLink";
import { CustomIcon } from "lib/components/icon";
import type { ValidatorUptimeResponse } from "lib/services/validator";

interface PenaltyEventProps {
event: ValidatorUptimeResponse["events"][0];
}
export const PenaltyEvent = ({ event }: PenaltyEventProps) => (
<Flex alignItems="center" gap={2}>
<Flex alignItems="center" gap={1}>
<CustomIcon
name="alert-triangle"
color={event.isJailed ? "warning.main" : "error.main"}
/>
<Text variant="body2" color="text.main">
{event.isJailed ? "Jailed" : "Slashed"} at block height
</Text>
<ExplorerLink
type="block_height"
value={event.height.toString()}
showCopyOnHover
/>
</Flex>
</Flex>
);
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { Flex, Heading, Text } from "@chakra-ui/react";

import { PenaltyStatus } from "../../types";
import type { ValidatorUptimeResponse } from "lib/services/validator";

import { PenaltyStatusSection } from "./PenaltyStatusSection";
import { PenaltyEvent } from "./PenaltyEvent";

export const PenaltySection = () => (
interface PenaltySectionProps {
penaltyEvents: ValidatorUptimeResponse["events"];
}

export const PenaltySection = ({ penaltyEvents }: PenaltySectionProps) => (
<Flex
direction="column"
gap={4}
Expand All @@ -22,9 +26,15 @@ export const PenaltySection = () => (
</Text>
</Flex>
<Flex direction="column" gap={2}>
<PenaltyStatusSection status={PenaltyStatus.Jailed} />
<PenaltyStatusSection status={PenaltyStatus.Slashed} />
<PenaltyStatusSection status={PenaltyStatus.Jailed} />
{penaltyEvents.length === 0 ? (
<Text variant="body2" color="text.dark">
This validator never had any slash or jailed history within 90 days.
</Text>
) : (
penaltyEvents.map((event) => (
<PenaltyEvent key={event.height} event={event} />
))
)}
</Flex>
</Flex>
);

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import { Flex, Text } from "@chakra-ui/react";

import type { ComputedUptime, Ratio } from "lib/types";
import { formatRatio } from "lib/utils";

const LegendItem = ({
label,
color,
value,
percent,
ratio,
}: {
label: string;
color: string;
value: string;
percent: string;
value: number;
ratio: Ratio<number>;
}) => (
<Flex gap={2} w="full">
<Flex w={3} h={3} borderRadius="2px" backgroundColor={color} mt={1} />
Expand All @@ -34,31 +37,38 @@ const LegendItem = ({
{value}
</Text>
<Text variant="body3" color="text.dark">
{percent}
{formatRatio(ratio)}
</Text>
</Flex>
</Flex>
);
export const RecentBlocksLegends = () => {

interface RecentBlocksLegendsProps {
uptime: ComputedUptime;
}

export const RecentBlocksLegends = ({
uptime: { signed, proposed, missed, signedRatio, proposedRatio, missedRatio },
}: RecentBlocksLegendsProps) => {
return (
<Flex direction={{ base: "column", md: "row" }}>
<LegendItem
label="Signed Blocks"
color="primary.main"
value="88"
percent="95.21%"
value={signed}
ratio={signedRatio}
/>
<LegendItem
label="Proposed Blocks"
color="secondary.main"
value="12"
percent="3.00%"
value={proposed}
ratio={proposedRatio}
/>
<LegendItem
label="Missed Blocks"
color="error.dark"
value="3"
percent="1.11%"
value={missed}
ratio={missedRatio}
/>
</Flex>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,24 +1,71 @@
import { Button, Flex, Heading, Text } from "@chakra-ui/react";
import {
Button,
Divider,
Flex,
Heading,
Menu,
MenuButton,
MenuItem,
MenuList,
Text,
} from "@chakra-ui/react";
import { isUndefined } from "lodash";
import { useMemo } from "react";

import { PenaltyStatus } from "../../types";
import { useMobile } from "lib/app-provider";
import { CustomIcon } from "lib/components/icon";
import { Loading } from "lib/components/Loading";
import { ValueWithIcon } from "lib/components/ValueWithIcon";
import type { ValidatorUptimeResponse } from "lib/services/validator";
import type { ComputedUptime, Option, Ratio } from "lib/types";
import { formatRatio } from "lib/utils";

import { PenaltyStatusSection } from "./PenaltyStatusSection";
import { PenaltyEvent } from "./PenaltyEvent";
import { RecentBlocksLegends } from "./RecentBlocksLegends";
import { RecentBlocksSection } from "./RecentBlocksSection";

interface UptimeSectionProps {
isDetailPage?: boolean;
uptimeData: Option<ValidatorUptimeResponse>;
uptimeBlock: number;
setUptimeBlock?: (block: number) => void;
onViewMore?: () => void;
}

export const UptimeSection = ({
uptimeData,
uptimeBlock,
setUptimeBlock,
onViewMore,
isDetailPage = false,
}: UptimeSectionProps) => {
const isMobile = useMobile();

const computed = useMemo<ComputedUptime>(() => {
const data = uptimeData?.uptime;
if (!data)
return {
signed: 0,
proposed: 0,
missed: 0,
signedRatio: 0 as Ratio<number>,
proposedRatio: 0 as Ratio<number>,
missedRatio: 0 as Ratio<number>,
uptimeRatio: 0 as Ratio<number>,
};

return {
signed: data.signedBlocks,
proposed: data.proposedBlocks,
missed: data.missedBlocks,
signedRatio: (data.signedBlocks / data.total) as Ratio<number>,
proposedRatio: (data.proposedBlocks / data.total) as Ratio<number>,
missedRatio: (data.missedBlocks / data.total) as Ratio<number>,
uptimeRatio: ((data.signedBlocks + data.proposedBlocks) /
data.total) as Ratio<number>,
};
}, [uptimeData?.uptime]);

if (isUndefined(uptimeData)) return <Loading />;

return (
<Flex
direction="column"
Expand All @@ -34,9 +81,31 @@ export const UptimeSection = ({
<Heading variant="h6" as="h6" color="text.main">
Uptime
</Heading>
<Text variant="body2" color="text.dark">
Latest 100 Blocks
</Text>
{setUptimeBlock ? (
<Menu>
<MenuButton>
<Text variant="body2" color="text.dark">
Latest {uptimeBlock} Blocks
<CustomIcon name="chevron-down" ml={2} />
</Text>
</MenuButton>
<MenuList>
<MenuItem onClick={() => setUptimeBlock(100)}>
Latest 100 Blocks
</MenuItem>
<MenuItem onClick={() => setUptimeBlock(1000)}>
Latest 1000 Blocks
</MenuItem>
<MenuItem onClick={() => setUptimeBlock(10000)}>
Latest 10000 Blocks
</MenuItem>
</MenuList>
</Menu>
) : (
<Text variant="body2" color="text.dark">
Latest 100 Blocks
</Text>
)}
</Flex>
{onViewMore && !isMobile && (
<Button
Expand All @@ -48,13 +117,16 @@ export const UptimeSection = ({
</Button>
)}
</Flex>
<ValueWithIcon icon="block" value="98.21%" />
<ValueWithIcon icon="block" value={formatRatio(computed.uptimeRatio)} />
</Flex>
<RecentBlocksLegends />
{!isDetailPage && (
<RecentBlocksLegends uptime={computed} />
{onViewMore && (
<>
{isMobile && <RecentBlocksSection />}
<PenaltyStatusSection hasBorder status={PenaltyStatus.Jailed} />{" "}
{uptimeData.events.length !== 0 && <Divider />}
{uptimeData.events.map((event) => (
<PenaltyEvent key={event.height} event={event} />
))}
</>
)}
{onViewMore && isMobile && (
Expand Down
57 changes: 39 additions & 18 deletions src/lib/pages/validator-details/components/performance/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
import { Flex } from "@chakra-ui/react";
import { isUndefined } from "lodash";
import { useState } from "react";

import { ProposedBlocksTable } from "../tables/ProposedBlocksTable";
import { Loading } from "lib/components/Loading";
import { ErrorFetching } from "lib/components/state";
import { useValidatorUptime } from "lib/services/validatorService";
import type { ValidatorAddr } from "lib/types";

import { PenaltySection } from "./PenaltySection";
Expand All @@ -11,24 +16,40 @@ interface PerformanceProps {
validatorAddress: ValidatorAddr;
}

export const Performance = ({ validatorAddress }: PerformanceProps) => (
<Flex direction="column" gap={{ base: 4, md: 6 }} pt={6}>
<Flex gap={{ base: 4, md: 6 }} direction={{ base: "column", md: "row" }}>
<Flex flex={{ md: "2" }}>
<UptimeSection isDetailPage />
export const Performance = ({ validatorAddress }: PerformanceProps) => {
const [uptimeBlock, setUptimeBlock] = useState(100);
const { data: uptimeData, isLoading } = useValidatorUptime(
validatorAddress,
uptimeBlock
);

if (isLoading) return <Loading />;
if (isUndefined(uptimeData))
return <ErrorFetching dataName="performance data" />;

return (
<Flex direction="column" gap={{ base: 4, md: 6 }} pt={6}>
<Flex gap={{ base: 4, md: 6 }} direction={{ base: "column", md: "row" }}>
<Flex flex={{ md: "2" }}>
<UptimeSection
uptimeData={uptimeData}
uptimeBlock={uptimeBlock}
setUptimeBlock={(block) => setUptimeBlock(block)}
/>
</Flex>
<Flex flex={{ md: "1" }}>
<PenaltySection penaltyEvents={uptimeData.events} />
</Flex>
</Flex>
<Flex flex={{ md: "1" }}>
<PenaltySection />
<Flex
backgroundColor="gray.900"
p={{ base: 4, md: 6 }}
rounded={8}
w="100%"
>
<RecentBlocksSection hasTitle />
</Flex>
<ProposedBlocksTable validatorAddress={validatorAddress} />
</Flex>
<Flex
backgroundColor="gray.900"
p={{ base: 4, md: 6 }}
rounded={8}
w="100%"
>
<RecentBlocksSection hasTitle />
</Flex>
<ProposedBlocksTable validatorAddress={validatorAddress} />
</Flex>
);
);
};
Loading

0 comments on commit f65c634

Please sign in to comment.