Skip to content

Commit

Permalink
Merge pull request #823 from alleslabs/feat/validator-recent-100-block
Browse files Browse the repository at this point in the history
validator uptime & penalty events
  • Loading branch information
evilpeach committed Mar 20, 2024
2 parents fe8e9c4 + c4ed0ee commit 00c8b89
Show file tree
Hide file tree
Showing 14 changed files with 235 additions and 133 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Features

- [#823](https://github.com/alleslabs/celatone-frontend/pull/823) Add validator uptime and penalty events
- [#822](https://github.com/alleslabs/celatone-frontend/pull/822) Add validator list page
- [#819](https://github.com/alleslabs/celatone-frontend/pull/819) Add validator detail overview section with data from APIs
- [#821](https://github.com/alleslabs/celatone-frontend/pull/821) Add Validator's proposed blocks table
Expand Down
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 { formatPrettyPercent } 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,32 +37,37 @@ const LegendItem = ({
{value}
</Text>
<Text variant="body3" color="text.dark">
{percent}
{formatPrettyPercent(ratio)}
</Text>
</Flex>
</Flex>
);
export const RecentBlocksLegends = () => {
return (
<Flex direction={{ base: "column", md: "row" }}>
<LegendItem
label="Signed Blocks"
color="primary.main"
value="88"
percent="95.21%"
/>
<LegendItem
label="Proposed Blocks"
color="secondary.main"
value="12"
percent="3.00%"
/>
<LegendItem
label="Missed Blocks"
color="error.dark"
value="3"
percent="1.11%"
/>
</Flex>
);
};

interface RecentBlocksLegendsProps {
uptime: ComputedUptime;
}

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

import { PenaltyStatus } from "../../types";
import { useMobile } from "lib/app-provider";
import { CustomIcon } from "lib/components/icon";
import { ValueWithIcon } from "lib/components/ValueWithIcon";
import type { ValidatorUptimeResponse } from "lib/services/validator";
import type { ComputedUptime, 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: 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;

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>,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [JSON.stringify(uptimeData.uptime)]);

return (
<Flex
direction="column"
Expand All @@ -34,9 +68,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,24 +104,27 @@ 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} />
))}
{isMobile && (
<Button
variant="ghost-primary"
rightIcon={<CustomIcon name="chevron-right" />}
onClick={onViewMore}
>
View Performance
</Button>
)}
</>
)}
{onViewMore && isMobile && (
<Button
variant="ghost-primary"
rightIcon={<CustomIcon name="chevron-right" />}
onClick={onViewMore}
>
View Performance
</Button>
)}
</Flex>
);
};
Loading

0 comments on commit 00c8b89

Please sign in to comment.