Skip to content

Commit

Permalink
feat: simple referenda page
Browse files Browse the repository at this point in the history
  • Loading branch information
tien committed Feb 26, 2025
1 parent 68c0606 commit 408e7e0
Show file tree
Hide file tree
Showing 14 changed files with 1,011 additions and 73 deletions.
35 changes: 31 additions & 4 deletions panda.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import { defineConfig } from "@pandacss/dev";
import { createPreset } from "@park-ui/panda-preset";
import amber from "@park-ui/panda-preset/colors/amber";
import blue from "@park-ui/panda-preset/colors/blue";
import crimson from "@park-ui/panda-preset/colors/crimson";
import green from "@park-ui/panda-preset/colors/green";
import neutral from "@park-ui/panda-preset/colors/neutral";
import red from "@park-ui/panda-preset/colors/red";
import violet from "@park-ui/panda-preset/colors/violet";
import yellow from "@park-ui/panda-preset/colors/yellow";

export default defineConfig({
Expand All @@ -20,30 +22,55 @@ export default defineConfig({
extend: {
tokens: {
colors: {
[red.name]: red.tokens,
[green.name]: green.tokens,
[blue.name]: blue.tokens,
[yellow.name]: yellow.tokens,
[red.name]: red.tokens,
[amber.name]: amber.tokens,
success: green.tokens,
info: blue.tokens,
warning: yellow.tokens,
warning: amber.tokens,
error: red.tokens,
violet: violet.tokens,
},
},
semanticTokens: {
colors: {
[red.name]: red.semanticTokens,
[green.name]: green.semanticTokens,
[blue.name]: blue.semanticTokens,
[yellow.name]: yellow.semanticTokens,
[red.name]: red.semanticTokens,
[amber.name]: amber.semanticTokens,
success: green.semanticTokens,
info: blue.semanticTokens,
warning: yellow.semanticTokens,
warning: amber.semanticTokens,
error: red.semanticTokens,
violet: violet.semanticTokens,
},
},
},
},
staticCss: {
css: [
{
properties: {
colorPalette: [
"success",
"info",
"warning",
"error",
"violet",
"red",
"green",
"blue",
"yellow",
"amber",
"violet",
],
},
},
],
},
include: ["./src/**/*.{js,jsx,ts,tsx}"],
jsxFramework: "react",
outdir: "styled-system",
Expand Down
50 changes: 50 additions & 0 deletions src/components/info-header.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import { Heading } from "./ui/heading";
import type { PropsWithChildren, ReactNode } from "react";
import { css, cx } from "styled-system/css";

type InfoHeaderProps = PropsWithChildren<{
className?: string | undefined;
}>;

export const InfoHeader = Object.assign(
({ className, children }: InfoHeaderProps) => (
<section
className={cx(
className,
css({
display: "flex",
flexDirection: "column",
padding: "1rem",
"&>*": {
flex: 1,
padding: "0 1rem",
},
"@media(min-width: 68rem)": {
flexDirection: "row",
"&>*:not(:first-child)": {
borderLeft: "1px solid",
},
},
}),
)}
>
{children}
</section>
),
{ Item: InfoHeaderItem },
);

type InfoHeaderItemProps = PropsWithChildren<{
title: ReactNode;
}>;

function InfoHeaderItem({ title, children }: InfoHeaderItemProps) {
return (
<article>
<header>
<Heading as="h3">{title}</Heading>
</header>
<div>{children}</div>
</article>
);
}
196 changes: 196 additions & 0 deletions src/features/coretime/components/parachains.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
import { QueryRenderer, useLazyLoadQuery } from "@reactive-dot/react";
import { Suspense } from "react";
import { Center } from "styled-system/jsx";
import { CircularProgressIndicator } from "~/components/circular-progress-indicator";
import { InfoHeader } from "~/components/info-header";
import { Badge } from "~/components/ui/badge";
import { Table } from "~/components/ui/table";
import { useRelayChainId } from "~/hooks/chain";

export function Parachains() {
return (
<section>
<InfoHeader>
<InfoHeader.Item title="Parachains">
<Suspense fallback={<CircularProgressIndicator />}>
<QueryRenderer
chainId={useRelayChainId()}
query={(builder) =>
builder.readStorageEntries("Paras", "ParaLifecycles", [])
}
>
{(lifecycles) => lifecycles.length.toLocaleString()}
</QueryRenderer>
</Suspense>
</InfoHeader.Item>
<InfoHeader.Item title="Cores">
<Suspense fallback={<CircularProgressIndicator />}>
<QueryRenderer
chainId="polkadot_coretime"
query={(builder) => builder.readStorage("Broker", "Status", [])}
>
{(data) => data?.core_count.toLocaleString() ?? "N/A"}
</QueryRenderer>
</Suspense>
</InfoHeader.Item>
</InfoHeader>
<Suspense
fallback={
<Center margin="2rem">
<CircularProgressIndicator label="Loading parachains" />
</Center>
}
>
<ParachainsTable />
</Suspense>
</section>
);
}

function ParachainsTable() {
const [
[paraLifecycles, freeOnDemandEntries, affinityOnDemandEntries],
[workloads, reservations, leases],
] = useLazyLoadQuery([
{
chainId: "polkadot",
query: (builder) =>
builder
.readStorageEntries("Paras", "ParaLifecycles", [])
.readStorage("OnDemand", "FreeEntries", [])
.readStorageEntries("OnDemand", "AffinityEntries", []),
},
{
chainId: "polkadot_coretime",
query: (builder) =>
builder
.readStorageEntries("Broker", "Workload", [])
.readStorage("Broker", "Reservations", [])
.readStorage("Broker", "Leases", []),
},
]);

const systemParaIds = new Set(
reservations
.flat()
.map((reservation) => reservation.assignment)
.filter((x) => x.type === "Task")
.map((assignment) => assignment.value),
);

const legacyParaIds = new Set(leases.map((lease) => lease.task));

const taskCoresMap = workloads
.flatMap(([[core], loads]) =>
loads.map((load) => ({ core, ...load.assignment })),
)
.filter((assignment) => assignment.type === "Task")
.reduce(
(prev, curr) =>
prev.set(curr.value, (prev.get(curr.core) ?? new Set()).add(curr.core)),
new Map<number, Set<number>>(),
);

const onDemandCoresMap = affinityOnDemandEntries
.flatMap(([[core], entries]) =>
entries.map((entry) => ({ core, paraId: entry.para_id })),
)
.reduce(
(prev, curr) =>
prev.set(
curr.paraId,
(prev.get(curr.core) ?? new Set()).add(curr.core),
),
new Map<number, Set<number>>(),
);

const activeOnDemandParaIds = new Set(
freeOnDemandEntries
.map((entry) => entry.para_id)
.concat(
affinityOnDemandEntries.flatMap(([_, entries]) =>
entries.map((entry) => entry.para_id),
),
),
);

const parachains = paraLifecycles
.toSorted(([[a]], [[b]]) => a - b)
.map(([[paraId]]) => {
const type = (() => {
if (systemParaIds.has(paraId)) {
return "System parachain" as const;
}

if (legacyParaIds.has(paraId)) {
return "Legacy parachain" as const;
}

if (taskCoresMap.has(paraId)) {
return "Parachain" as const;
}

if (onDemandCoresMap.has(paraId)) {
return "On-demand parachain" as const;
}

return "On-demand parachain" as const;
})();

return {
type,
id: paraId,
active: taskCoresMap.has(paraId) || activeOnDemandParaIds.has(paraId),
cores: Array.from(
(taskCoresMap.get(paraId) ?? new Set())?.union(
onDemandCoresMap.get(paraId) ?? new Set(),
) ?? [],
),
};
});

return (
<Table.Root>
<Table.Head>
<Table.Row>
<Table.Header>Id</Table.Header>
<Table.Header>Type</Table.Header>
<Table.Header>Status</Table.Header>
<Table.Header>Core ID</Table.Header>
</Table.Row>
</Table.Head>
<Table.Body>
{parachains.map((parachain) => (
<Table.Row key={parachain.id}>
<Table.Cell>{parachain.id.toLocaleString()}</Table.Cell>
<Table.Cell
colorPalette={`${(() => {
switch (parachain.type) {
case "System parachain":
return "current";
case "Parachain":
return "green";
case "Legacy parachain":
return "violet";
case "On-demand parachain":
return "blue";
}
})()}`}
>
<Badge variant="solid">{parachain.type}</Badge>
</Table.Cell>
<Table.Cell
colorPalette={parachain.active ? "success" : "error"}
color="colorPalette.text"
>
{parachain.active ? "Active" : "Inactive"}
</Table.Cell>
<Table.Cell>
{parachain.cores.length > 0 ? parachain.cores.join(", ") : "N/A"}
</Table.Cell>
</Table.Row>
))}
</Table.Body>
</Table.Root>
);
}
Loading

0 comments on commit 408e7e0

Please sign in to comment.