mirror of
https://github.com/Xevion/rdap.git
synced 2025-12-06 01:16:00 -06:00
feat: enhance RDAP component rendering with comprehensive data display
Major improvements to RDAP card components: - Add tooltips to action buttons in AbstractCard for better UX - Implement dedicated section components (EntitiesSection, LinksSection, NameserversSection, RemarksSection, SecureDNSSection, VCardDisplay) - Add conditional rendering for optional fields across all card types - Enhance Entity and Nameserver cards with full data display - Add WHOIS server (port43) display to relevant cards - Improve visual hierarchy with nested entity displays - Fix autodetection to only run when in autodetect mode - Add proper null/undefined checks throughout components
This commit is contained in:
@@ -2,7 +2,7 @@ import type { FunctionComponent, ReactNode } from "react";
|
||||
import React from "react";
|
||||
import { useBoolean } from "usehooks-ts";
|
||||
import { Link2Icon, CodeIcon, DownloadIcon, ClipboardIcon } from "@radix-ui/react-icons";
|
||||
import { Card, Flex, Box, IconButton, Code, ScrollArea } from "@radix-ui/themes";
|
||||
import { Card, Flex, Box, IconButton, Code, ScrollArea, Tooltip } from "@radix-ui/themes";
|
||||
|
||||
type AbstractCardProps = {
|
||||
children?: ReactNode;
|
||||
@@ -38,72 +38,85 @@ const AbstractCard: FunctionComponent<AbstractCardProps> = ({
|
||||
</Flex>
|
||||
<Flex gap="2" align="center">
|
||||
{url != undefined && (
|
||||
<IconButton variant="ghost" size="2" asChild>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label="Open RDAP URL"
|
||||
>
|
||||
<Link2Icon width="18" height="18" />
|
||||
</a>
|
||||
</IconButton>
|
||||
<Tooltip content="Open in new tab">
|
||||
<IconButton variant="ghost" size="2" asChild>
|
||||
<a
|
||||
href={url}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label="Open RDAP URL"
|
||||
>
|
||||
<Link2Icon width="18" height="18" />
|
||||
</a>
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
)}
|
||||
{data != undefined && (
|
||||
<>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify(data, null, 4))
|
||||
.then(
|
||||
() => {
|
||||
// Successfully copied to clipboard
|
||||
},
|
||||
(err) => {
|
||||
if (err instanceof Error)
|
||||
console.error(
|
||||
`Failed to copy to clipboard (${err.toString()}).`
|
||||
);
|
||||
else
|
||||
console.error(
|
||||
"Failed to copy to clipboard."
|
||||
);
|
||||
<Tooltip content="Copy JSON to clipboard">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={() => {
|
||||
navigator.clipboard
|
||||
.writeText(JSON.stringify(data, null, 4))
|
||||
.then(
|
||||
() => {
|
||||
// Successfully copied to clipboard
|
||||
},
|
||||
(err) => {
|
||||
if (err instanceof Error)
|
||||
console.error(
|
||||
`Failed to copy to clipboard (${err.toString()}).`
|
||||
);
|
||||
else
|
||||
console.error(
|
||||
"Failed to copy to clipboard."
|
||||
);
|
||||
}
|
||||
);
|
||||
}}
|
||||
aria-label="Copy JSON to clipboard"
|
||||
>
|
||||
<ClipboardIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip content="Download JSON">
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={() => {
|
||||
const file = new Blob(
|
||||
[JSON.stringify(data, null, 4)],
|
||||
{
|
||||
type: "application/json",
|
||||
}
|
||||
);
|
||||
}}
|
||||
aria-label="Copy JSON to clipboard"
|
||||
>
|
||||
<ClipboardIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={() => {
|
||||
const file = new Blob([JSON.stringify(data, null, 4)], {
|
||||
type: "application/json",
|
||||
});
|
||||
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = URL.createObjectURL(file);
|
||||
anchor.download = "response.json";
|
||||
anchor.click();
|
||||
}}
|
||||
aria-label="Download JSON"
|
||||
const anchor = document.createElement("a");
|
||||
anchor.href = URL.createObjectURL(file);
|
||||
anchor.download = "response.json";
|
||||
anchor.click();
|
||||
}}
|
||||
aria-label="Download JSON"
|
||||
>
|
||||
<DownloadIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
content={showRaw ? "Show formatted view" : "Show raw JSON"}
|
||||
>
|
||||
<DownloadIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={toggleRaw}
|
||||
aria-label={
|
||||
showRaw ? "Show formatted view" : "Show raw JSON"
|
||||
}
|
||||
>
|
||||
<CodeIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
<IconButton
|
||||
variant="ghost"
|
||||
size="2"
|
||||
onClick={toggleRaw}
|
||||
aria-label={
|
||||
showRaw ? "Show formatted view" : "Show raw JSON"
|
||||
}
|
||||
>
|
||||
<CodeIcon width="18" height="18" />
|
||||
</IconButton>
|
||||
</Tooltip>
|
||||
</>
|
||||
)}
|
||||
</Flex>
|
||||
|
||||
@@ -70,11 +70,14 @@ const Index: NextPage = () => {
|
||||
setTarget(target);
|
||||
setTargetType(targetType);
|
||||
|
||||
const detectResult = await getType(target);
|
||||
if (detectResult.isOk) {
|
||||
setDetectedType(Maybe.just(detectResult.value));
|
||||
} else {
|
||||
setDetectedType(Maybe.nothing());
|
||||
// Only run autodetection when in autodetect mode (targetType is null)
|
||||
if (targetType === null) {
|
||||
const detectResult = await getType(target);
|
||||
if (detectResult.isOk) {
|
||||
setDetectedType(Maybe.just(detectResult.value));
|
||||
} else {
|
||||
setDetectedType(Maybe.nothing());
|
||||
}
|
||||
}
|
||||
}}
|
||||
onSubmit={async function (props) {
|
||||
|
||||
@@ -6,6 +6,9 @@ import Property from "@/components/Property";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
import AbstractCard from "@/components/AbstractCard";
|
||||
import EntitiesSection from "@/rdap/components/EntitiesSection";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
import RemarksSection from "@/rdap/components/RemarksSection";
|
||||
import { Flex, Text, DataList, Badge, Code } from "@radix-ui/themes";
|
||||
|
||||
export type AutnumCardProps = {
|
||||
@@ -50,21 +53,51 @@ const AutnumCard: FunctionComponent<AutnumCardProps> = ({ data, url }: AutnumCar
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
<Property title="Type">{data.type}</Property>
|
||||
<Property title="Country">{data.country.toUpperCase()}</Property>
|
||||
<Property title="Events">
|
||||
<Events key={0} data={data.events} />
|
||||
</Property>
|
||||
<DataList.Item align="center">
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status, index) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
{data.type && <Property title="Type">{data.type}</Property>}
|
||||
{data.country && <Property title="Country">{data.country.toUpperCase()}</Property>}
|
||||
{data.status && data.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status, index) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.port43 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>WHOIS Server</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.port43}</Code>
|
||||
<CopyButton value={data.port43} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.entities && data.entities.length > 0 && (
|
||||
<Property title="Entities">
|
||||
<EntitiesSection entities={data.entities} />
|
||||
</Property>
|
||||
)}
|
||||
{data.events && data.events.length > 0 && (
|
||||
<Property title="Events">
|
||||
<Events data={data.events} />
|
||||
</Property>
|
||||
)}
|
||||
{data.links && data.links.length > 0 && (
|
||||
<Property title="Links">
|
||||
<LinksSection links={data.links} />
|
||||
</Property>
|
||||
)}
|
||||
{data.remarks && data.remarks.length > 0 && (
|
||||
<Property title="Remarks">
|
||||
<RemarksSection remarks={data.remarks} />
|
||||
</Property>
|
||||
)}
|
||||
</DataList.Root>
|
||||
</AbstractCard>
|
||||
);
|
||||
|
||||
@@ -6,6 +6,11 @@ import Property from "@/components/Property";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
import AbstractCard from "@/components/AbstractCard";
|
||||
import NameserversSection from "@/rdap/components/NameserversSection";
|
||||
import SecureDNSSection from "@/rdap/components/SecureDNSSection";
|
||||
import EntitiesSection from "@/rdap/components/EntitiesSection";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
import RemarksSection from "@/rdap/components/RemarksSection";
|
||||
import { Flex, Text, DataList, Badge, Code } from "@radix-ui/themes";
|
||||
|
||||
export type DomainProps = {
|
||||
@@ -48,28 +53,75 @@ const DomainCard: FunctionComponent<DomainProps> = ({ data, url }: DomainProps)
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
<DataList.Item>
|
||||
<DataList.Label>Handle</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.handle}</Code>
|
||||
<CopyButton value={data.handle} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
<Property title="Events">
|
||||
<Events key={0} data={data.events} />
|
||||
</Property>
|
||||
<DataList.Item align="center">
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((statusKey, index) => (
|
||||
<StatusBadge key={index} status={statusKey} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
{data.handle != undefined ? (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Handle</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.handle}</Code>
|
||||
<CopyButton value={data.handle} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
) : null}
|
||||
{data.status && data.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((statusKey, index) => (
|
||||
<StatusBadge key={index} status={statusKey} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.port43 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>WHOIS Server</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.port43}</Code>
|
||||
<CopyButton value={data.port43} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.nameservers && data.nameservers.length > 0 && (
|
||||
<Property title="Nameservers">
|
||||
<NameserversSection nameservers={data.nameservers} />
|
||||
</Property>
|
||||
)}
|
||||
{data.secureDNS && (
|
||||
<Property title="DNSSEC">
|
||||
<SecureDNSSection secureDNS={data.secureDNS} />
|
||||
</Property>
|
||||
)}
|
||||
{data.entities && data.entities.length > 0 && (
|
||||
<Property title="Entities">
|
||||
<EntitiesSection entities={data.entities} />
|
||||
</Property>
|
||||
)}
|
||||
{data.events && data.events.length > 0 && (
|
||||
<Property title="Events">
|
||||
<Events data={data.events} />
|
||||
</Property>
|
||||
)}
|
||||
{data.links && data.links.length > 0 && (
|
||||
<Property title="Links">
|
||||
<LinksSection links={data.links} />
|
||||
</Property>
|
||||
)}
|
||||
{data.notices && data.notices.length > 0 && (
|
||||
<Property title="Notices">
|
||||
<RemarksSection remarks={data.notices} />
|
||||
</Property>
|
||||
)}
|
||||
{data.remarks && data.remarks.length > 0 && (
|
||||
<Property title="Remarks">
|
||||
<RemarksSection remarks={data.remarks} />
|
||||
</Property>
|
||||
)}
|
||||
</DataList.Root>
|
||||
</AbstractCard>
|
||||
);
|
||||
|
||||
147
src/rdap/components/EntitiesSection.tsx
Normal file
147
src/rdap/components/EntitiesSection.tsx
Normal file
@@ -0,0 +1,147 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Entity, RdapStatusType } from "@/rdap/schemas";
|
||||
import { Box, Flex, Badge, Text, Code, DataList, Table } from "@radix-ui/themes";
|
||||
import VCardDisplay from "@/rdap/components/VCardDisplay";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
|
||||
export type EntitiesSectionProps = {
|
||||
entities: Entity[];
|
||||
};
|
||||
|
||||
const EntitiesSection: FunctionComponent<EntitiesSectionProps> = ({ entities }) => {
|
||||
if (!entities || entities.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="3">
|
||||
{entities.map((entity, index) => {
|
||||
return (
|
||||
<Box
|
||||
key={index}
|
||||
p="3"
|
||||
style={{
|
||||
border: "1px solid var(--gray-a5)",
|
||||
borderRadius: "var(--radius-3)",
|
||||
backgroundColor: "var(--gray-a2)",
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap="3">
|
||||
<DataList.Root
|
||||
orientation={{ initial: "vertical", sm: "horizontal" }}
|
||||
size="2"
|
||||
>
|
||||
{entity.handle && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Handle</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{entity.handle}</Code>
|
||||
<CopyButton value={entity.handle} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{entity.roles && entity.roles.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Roles</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{entity.roles.map(
|
||||
(role: string, roleIndex: number) => (
|
||||
<Badge
|
||||
key={roleIndex}
|
||||
variant="soft"
|
||||
size="1"
|
||||
>
|
||||
{role}
|
||||
</Badge>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{entity.status && entity.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{entity.status.map(
|
||||
(
|
||||
status: RdapStatusType,
|
||||
statusIndex: number
|
||||
) => (
|
||||
<StatusBadge
|
||||
key={statusIndex}
|
||||
status={status}
|
||||
/>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
</DataList.Root>
|
||||
|
||||
{entity.vcardArray && (
|
||||
<Flex direction="column" gap="2">
|
||||
<VCardDisplay vcardArray={entity.vcardArray} />
|
||||
</Flex>
|
||||
)}
|
||||
|
||||
{entity.publicIds && entity.publicIds.length > 0 && (
|
||||
<Table.Root size="1" variant="surface">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell>
|
||||
Public ID Type
|
||||
</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>
|
||||
Identifier
|
||||
</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{entity.publicIds.map(
|
||||
(
|
||||
publicId: { type: string; identifier: string },
|
||||
publicIdIndex: number
|
||||
) => (
|
||||
<Table.Row key={publicIdIndex}>
|
||||
<Table.Cell>{publicId.type}</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">
|
||||
{publicId.identifier}
|
||||
</Code>
|
||||
<CopyButton
|
||||
value={publicId.identifier}
|
||||
/>
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
)}
|
||||
|
||||
{entity.port43 && (
|
||||
<Flex align="center" gap="2">
|
||||
<Text size="2" weight="medium">
|
||||
WHOIS Server:
|
||||
</Text>
|
||||
<Code variant="ghost">{entity.port43}</Code>
|
||||
<CopyButton value={entity.port43} />
|
||||
</Flex>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
);
|
||||
})}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default EntitiesSection;
|
||||
@@ -1,8 +1,15 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Entity } from "@/rdap/schemas";
|
||||
import type { Entity, RdapStatusType } from "@/rdap/schemas";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
import AbstractCard from "@/components/AbstractCard";
|
||||
import Property from "@/components/Property";
|
||||
import VCardDisplay from "@/rdap/components/VCardDisplay";
|
||||
import Events from "@/rdap/components/Events";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
import RemarksSection from "@/rdap/components/RemarksSection";
|
||||
import EntitiesSection from "@/rdap/components/EntitiesSection";
|
||||
import { Flex, DataList, Badge, Text, Code } from "@radix-ui/themes";
|
||||
|
||||
export type EntityCardProps = {
|
||||
@@ -17,7 +24,9 @@ const EntityCard: FunctionComponent<EntityCardProps> = ({ data, url }: EntityCar
|
||||
url={url}
|
||||
header={
|
||||
<Flex gap="2" align="center" wrap="wrap">
|
||||
<Text size="5">{data.handle || data.roles.join(", ")}</Text>
|
||||
<Text size="5">
|
||||
{data.handle || (data.roles && data.roles.join(", ")) || "Entity"}
|
||||
</Text>
|
||||
<Badge color="gray">ENTITY</Badge>
|
||||
</Flex>
|
||||
}
|
||||
@@ -34,35 +43,90 @@ const EntityCard: FunctionComponent<EntityCardProps> = ({ data, url }: EntityCar
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
<DataList.Item align="center">
|
||||
<DataList.Label>Roles</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.roles.map((role, index) => (
|
||||
<Badge key={index} color="gray" variant="soft" radius="full">
|
||||
{role}
|
||||
</Badge>
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
{data.publicIds && data.publicIds.length > 0 && (
|
||||
<DataList.Item align="center">
|
||||
<DataList.Label>Public IDs</DataList.Label>
|
||||
{data.roles && data.roles.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Roles</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex direction="column" gap="2">
|
||||
{data.publicIds.map((publicId, index) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Code variant="ghost">
|
||||
{publicId.identifier} ({publicId.type})
|
||||
</Code>
|
||||
<CopyButton value={publicId.identifier} />
|
||||
</Flex>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.roles.map((role: string, index: number) => (
|
||||
<Badge key={index} color="gray" variant="soft" radius="full">
|
||||
{role}
|
||||
</Badge>
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.status && data.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status: RdapStatusType, index: number) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.publicIds && data.publicIds.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Public IDs</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex direction="column" gap="2">
|
||||
{data.publicIds.map(
|
||||
(
|
||||
publicId: { type: string; identifier: string },
|
||||
index: number
|
||||
) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Code variant="ghost">
|
||||
{publicId.identifier} ({publicId.type})
|
||||
</Code>
|
||||
<CopyButton value={publicId.identifier} />
|
||||
</Flex>
|
||||
)
|
||||
)}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.port43 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>WHOIS Server</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.port43}</Code>
|
||||
<CopyButton value={data.port43} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.vcardArray && (
|
||||
<Property title="Contact Information">
|
||||
<VCardDisplay vcardArray={data.vcardArray} />
|
||||
</Property>
|
||||
)}
|
||||
{data.entities && data.entities.length > 0 && (
|
||||
<Property title="Associated Entities">
|
||||
<EntitiesSection entities={data.entities} />
|
||||
</Property>
|
||||
)}
|
||||
{data.events && data.events.length > 0 && (
|
||||
<Property title="Events">
|
||||
<Events data={data.events} />
|
||||
</Property>
|
||||
)}
|
||||
{data.links && data.links.length > 0 && (
|
||||
<Property title="Links">
|
||||
<LinksSection links={data.links} />
|
||||
</Property>
|
||||
)}
|
||||
{data.remarks && data.remarks.length > 0 && (
|
||||
<Property title="Remarks">
|
||||
<RemarksSection remarks={data.remarks} />
|
||||
</Property>
|
||||
)}
|
||||
</DataList.Root>
|
||||
</AbstractCard>
|
||||
);
|
||||
|
||||
@@ -32,7 +32,7 @@ const Events: FunctionComponent<EventsProps> = ({ data }) => {
|
||||
{eventActor}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a6)" }}>
|
||||
<Text size="2" style={{ color: "var(--gray-a4)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
|
||||
@@ -6,6 +6,9 @@ import Property from "@/components/Property";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
import AbstractCard from "@/components/AbstractCard";
|
||||
import EntitiesSection from "@/rdap/components/EntitiesSection";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
import RemarksSection from "@/rdap/components/RemarksSection";
|
||||
import { Flex, Text, DataList, Badge, Code } from "@radix-ui/themes";
|
||||
|
||||
export type IPCardProps = {
|
||||
@@ -57,7 +60,7 @@ const IPCard: FunctionComponent<IPCardProps> = ({ data, url }: IPCardProps) => {
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
<Property title="Type">{data.type}</Property>
|
||||
{data.type && <Property title="Type">{data.type}</Property>}
|
||||
{data.country && <Property title="Country">{data.country}</Property>}
|
||||
{data.parentHandle && (
|
||||
<DataList.Item>
|
||||
@@ -70,19 +73,49 @@ const IPCard: FunctionComponent<IPCardProps> = ({ data, url }: IPCardProps) => {
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
<Property title="Events">
|
||||
<Events key={0} data={data.events} />
|
||||
</Property>
|
||||
<DataList.Item align="center">
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status, index) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
{data.status && data.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status, index) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.port43 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>WHOIS Server</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.port43}</Code>
|
||||
<CopyButton value={data.port43} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.entities && data.entities.length > 0 && (
|
||||
<Property title="Entities">
|
||||
<EntitiesSection entities={data.entities} />
|
||||
</Property>
|
||||
)}
|
||||
{data.events && data.events.length > 0 && (
|
||||
<Property title="Events">
|
||||
<Events data={data.events} />
|
||||
</Property>
|
||||
)}
|
||||
{data.links && data.links.length > 0 && (
|
||||
<Property title="Links">
|
||||
<LinksSection links={data.links} />
|
||||
</Property>
|
||||
)}
|
||||
{data.remarks && data.remarks.length > 0 && (
|
||||
<Property title="Remarks">
|
||||
<RemarksSection remarks={data.remarks} />
|
||||
</Property>
|
||||
)}
|
||||
</DataList.Root>
|
||||
</AbstractCard>
|
||||
);
|
||||
|
||||
69
src/rdap/components/LinksSection.tsx
Normal file
69
src/rdap/components/LinksSection.tsx
Normal file
@@ -0,0 +1,69 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Link as RdapLink } from "@/rdap/schemas";
|
||||
import { Table, Link, Text, Badge } from "@radix-ui/themes";
|
||||
|
||||
export type LinksSectionProps = {
|
||||
links: RdapLink[];
|
||||
};
|
||||
|
||||
const LinksSection: FunctionComponent<LinksSectionProps> = ({ links }) => {
|
||||
if (!links || links.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Table.Root size="1" variant="surface" layout="auto">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell>URL</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Relation</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Title</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Type</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{links.map((link, index) => (
|
||||
<Table.Row key={index}>
|
||||
<Table.Cell>
|
||||
<Link href={link.href} target="_blank" rel="noreferrer" size="2">
|
||||
{link.value || link.href}
|
||||
</Link>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{link.rel ? (
|
||||
<Badge variant="soft" size="1">
|
||||
{link.rel}
|
||||
</Badge>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a6)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{link.title ? (
|
||||
<Text size="2">{link.title}</Text>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a6)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{link.type ? (
|
||||
<Text size="2" color="gray">
|
||||
{link.type}
|
||||
</Text>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a6)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default LinksSection;
|
||||
@@ -2,7 +2,13 @@ import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Nameserver } from "@/rdap/schemas";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import StatusBadge from "@/components/StatusBadge";
|
||||
import AbstractCard from "@/components/AbstractCard";
|
||||
import Property from "@/components/Property";
|
||||
import Events from "@/rdap/components/Events";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
import RemarksSection from "@/rdap/components/RemarksSection";
|
||||
import EntitiesSection from "@/rdap/components/EntitiesSection";
|
||||
import { Flex, DataList, Badge, Text, Code } from "@radix-ui/themes";
|
||||
|
||||
export type NameserverCardProps = {
|
||||
@@ -26,8 +32,19 @@ const NameserverCard: FunctionComponent<NameserverCardProps> = ({
|
||||
}
|
||||
>
|
||||
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||
{data.unicodeName && data.unicodeName !== data.ldhName && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Unicode Name</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.unicodeName}</Code>
|
||||
<CopyButton value={data.unicodeName} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
<DataList.Item>
|
||||
<DataList.Label>LDH Name</DataList.Label>
|
||||
<DataList.Label>{data.unicodeName ? "LDH Name" : "Name"}</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.ldhName}</Code>
|
||||
@@ -35,6 +52,90 @@ const NameserverCard: FunctionComponent<NameserverCardProps> = ({
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
{data.handle && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Handle</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.handle}</Code>
|
||||
<CopyButton value={data.handle} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.ipAddresses?.v4 && data.ipAddresses.v4.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>IPv4 Addresses</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex direction="column" gap="1">
|
||||
{data.ipAddresses.v4.map((ip, index) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Code variant="ghost">{ip}</Code>
|
||||
<CopyButton value={ip} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.ipAddresses?.v6 && data.ipAddresses.v6.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>IPv6 Addresses</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex direction="column" gap="1">
|
||||
{data.ipAddresses.v6.map((ip, index) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Code variant="ghost">{ip}</Code>
|
||||
<CopyButton value={ip} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.status && data.status.length > 0 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Status</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex gap="2" wrap="wrap">
|
||||
{data.status.map((status, index) => (
|
||||
<StatusBadge key={index} status={status} />
|
||||
))}
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.port43 && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>WHOIS Server</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{data.port43}</Code>
|
||||
<CopyButton value={data.port43} />
|
||||
</Flex>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
{data.entities && data.entities.length > 0 && (
|
||||
<Property title="Entities">
|
||||
<EntitiesSection entities={data.entities} />
|
||||
</Property>
|
||||
)}
|
||||
{data.events && data.events.length > 0 && (
|
||||
<Property title="Events">
|
||||
<Events data={data.events} />
|
||||
</Property>
|
||||
)}
|
||||
{data.links && data.links.length > 0 && (
|
||||
<Property title="Links">
|
||||
<LinksSection links={data.links} />
|
||||
</Property>
|
||||
)}
|
||||
{data.remarks && data.remarks.length > 0 && (
|
||||
<Property title="Remarks">
|
||||
<RemarksSection remarks={data.remarks} />
|
||||
</Property>
|
||||
)}
|
||||
</DataList.Root>
|
||||
</AbstractCard>
|
||||
);
|
||||
|
||||
85
src/rdap/components/NameserversSection.tsx
Normal file
85
src/rdap/components/NameserversSection.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Nameserver } from "@/rdap/schemas";
|
||||
import { Table, Text, Code, Flex, Badge } from "@radix-ui/themes";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
|
||||
export type NameserversSectionProps = {
|
||||
nameservers: Nameserver[];
|
||||
};
|
||||
|
||||
const NameserversSection: FunctionComponent<NameserversSectionProps> = ({ nameservers }) => {
|
||||
if (!nameservers || nameservers.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Table.Root size="1" variant="surface" layout="auto">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell>Nameserver</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>IPv4 Addresses</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>IPv6 Addresses</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{nameservers.map((ns, index) => (
|
||||
<Table.Row key={index}>
|
||||
<Table.Cell>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{ns.ldhName}</Code>
|
||||
<CopyButton value={ns.ldhName} />
|
||||
</Flex>
|
||||
{ns.unicodeName && ns.unicodeName !== ns.ldhName && (
|
||||
<Flex align="center" gap="2" mt="1">
|
||||
<Badge variant="soft" size="1">
|
||||
Unicode
|
||||
</Badge>
|
||||
<Code variant="ghost" size="1">
|
||||
{ns.unicodeName}
|
||||
</Code>
|
||||
</Flex>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{ns.ipAddresses?.v4 && ns.ipAddresses.v4.length > 0 ? (
|
||||
<Flex direction="column" gap="1">
|
||||
{ns.ipAddresses.v4.map((ip, ipIndex) => (
|
||||
<Flex key={ipIndex} align="center" gap="2">
|
||||
<Code variant="ghost" size="1">
|
||||
{ip}
|
||||
</Code>
|
||||
<CopyButton value={ip} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a4)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
{ns.ipAddresses?.v6 && ns.ipAddresses.v6.length > 0 ? (
|
||||
<Flex direction="column" gap="1">
|
||||
{ns.ipAddresses.v6.map((ip, ipIndex) => (
|
||||
<Flex key={ipIndex} align="center" gap="2">
|
||||
<Code variant="ghost" size="1">
|
||||
{ip}
|
||||
</Code>
|
||||
<CopyButton value={ip} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
) : (
|
||||
<Text size="2" style={{ color: "var(--gray-a4)" }}>
|
||||
—
|
||||
</Text>
|
||||
)}
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default NameserversSection;
|
||||
61
src/rdap/components/RemarksSection.tsx
Normal file
61
src/rdap/components/RemarksSection.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { Remark, Notice } from "@/rdap/schemas";
|
||||
import { Box, Text, Heading, Flex, Badge } from "@radix-ui/themes";
|
||||
import LinksSection from "@/rdap/components/LinksSection";
|
||||
|
||||
export type RemarksSectionProps = {
|
||||
remarks?: Remark[] | Notice[];
|
||||
};
|
||||
|
||||
const RemarksSection: FunctionComponent<RemarksSectionProps> = ({ remarks }) => {
|
||||
if (!remarks || remarks.length === 0) return null;
|
||||
|
||||
return (
|
||||
<Flex direction="column" gap="3">
|
||||
{remarks.map((remark, index) => (
|
||||
<Box
|
||||
key={index}
|
||||
p="3"
|
||||
style={{
|
||||
borderLeft: "3px solid var(--accent-a5)",
|
||||
backgroundColor: "var(--gray-a2)",
|
||||
borderRadius: "var(--radius-3)",
|
||||
}}
|
||||
>
|
||||
<Flex direction="column" gap="2">
|
||||
{remark.title && (
|
||||
<Flex align="center" gap="2">
|
||||
<Heading size="3">{remark.title}</Heading>
|
||||
{remark.type && (
|
||||
<Badge variant="soft" size="1">
|
||||
{remark.type}
|
||||
</Badge>
|
||||
)}
|
||||
</Flex>
|
||||
)}
|
||||
{remark.description && remark.description.length > 0 && (
|
||||
<Flex direction="column" gap="1">
|
||||
{remark.description.map((desc, descIndex) => (
|
||||
<Text key={descIndex} size="2" style={{ lineHeight: "1.6" }}>
|
||||
{desc}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
)}
|
||||
{remark.links && remark.links.length > 0 && (
|
||||
<Box mt="2">
|
||||
<Text size="2" weight="medium" mb="1" style={{ display: "block" }}>
|
||||
Related Links
|
||||
</Text>
|
||||
<LinksSection links={remark.links} />
|
||||
</Box>
|
||||
)}
|
||||
</Flex>
|
||||
</Box>
|
||||
))}
|
||||
</Flex>
|
||||
);
|
||||
};
|
||||
|
||||
export default RemarksSection;
|
||||
152
src/rdap/components/SecureDNSSection.tsx
Normal file
152
src/rdap/components/SecureDNSSection.tsx
Normal file
@@ -0,0 +1,152 @@
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
import type { SecureDNS } from "@/rdap/schemas";
|
||||
import { Badge, Box, Code, DataList, Flex, Table, Text } from "@radix-ui/themes";
|
||||
import type { FunctionComponent } from "react";
|
||||
|
||||
export type SecureDNSSectionProps = {
|
||||
secureDNS: SecureDNS;
|
||||
};
|
||||
|
||||
const SecureDNSSection: FunctionComponent<SecureDNSSectionProps> = ({ secureDNS }) => {
|
||||
const hasData =
|
||||
secureDNS.zoneSigned !== undefined ||
|
||||
secureDNS.delegationSigned !== undefined ||
|
||||
secureDNS.maxSigLife !== undefined ||
|
||||
(secureDNS.dsData && secureDNS.dsData.length > 0) ||
|
||||
(secureDNS.keyData && secureDNS.keyData.length > 0);
|
||||
|
||||
if (!hasData) return null;
|
||||
|
||||
return (
|
||||
<Box>
|
||||
<DataList.Root orientation={{ initial: "vertical", sm: "horizontal" }} size="2">
|
||||
{secureDNS.zoneSigned !== undefined && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Zone Signed</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Badge color={secureDNS.zoneSigned ? "green" : "gray"} variant="soft">
|
||||
{secureDNS.zoneSigned ? "Yes" : "No"}
|
||||
</Badge>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
|
||||
{secureDNS.delegationSigned !== undefined && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Delegation Signed</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Badge
|
||||
color={secureDNS.delegationSigned ? "green" : "gray"}
|
||||
variant="soft"
|
||||
>
|
||||
{secureDNS.delegationSigned ? "Yes" : "No"}
|
||||
</Badge>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
|
||||
{secureDNS.maxSigLife !== undefined && (
|
||||
<DataList.Item>
|
||||
<DataList.Label>Max Signature Life</DataList.Label>
|
||||
<DataList.Value>
|
||||
<Text>{secureDNS.maxSigLife} seconds</Text>
|
||||
</DataList.Value>
|
||||
</DataList.Item>
|
||||
)}
|
||||
</DataList.Root>
|
||||
|
||||
{secureDNS.dsData && secureDNS.dsData.length > 0 && (
|
||||
<Table.Root size="1" variant="surface" mt="3">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell>Key Tag</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Algorithm</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Digest Type</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Digest</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{secureDNS.dsData.map((ds, index) => (
|
||||
<Table.Row key={index}>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{ds.keyTag}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{ds.algorithm}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{ds.digestType}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex align="center" gap="2">
|
||||
<Code
|
||||
variant="ghost"
|
||||
style={{
|
||||
maxWidth: "300px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{ds.digest}
|
||||
</Code>
|
||||
<CopyButton value={ds.digest} />
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
)}
|
||||
|
||||
{secureDNS.keyData && secureDNS.keyData.length > 0 && (
|
||||
<Box mt="4">
|
||||
<Text size="3" weight="medium" mb="2" style={{ display: "block" }}>
|
||||
Key Data
|
||||
</Text>
|
||||
<Table.Root size="1" variant="surface">
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.ColumnHeaderCell>Flags</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Protocol</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Algorithm</Table.ColumnHeaderCell>
|
||||
<Table.ColumnHeaderCell>Public Key</Table.ColumnHeaderCell>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{secureDNS.keyData.map((key, index) => (
|
||||
<Table.Row key={index}>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{key.flags}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{key.protocol}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Code variant="ghost">{key.algorithm}</Code>
|
||||
</Table.Cell>
|
||||
<Table.Cell>
|
||||
<Flex align="center" gap="2">
|
||||
<Code
|
||||
variant="ghost"
|
||||
style={{
|
||||
maxWidth: "300px",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
}}
|
||||
>
|
||||
{key.publicKey}
|
||||
</Code>
|
||||
<CopyButton value={key.publicKey} />
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
))}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default SecureDNSSection;
|
||||
239
src/rdap/components/VCardDisplay.tsx
Normal file
239
src/rdap/components/VCardDisplay.tsx
Normal file
@@ -0,0 +1,239 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import React from "react";
|
||||
import type { VCardArray } from "@/rdap/schemas";
|
||||
import { Table, Flex, Code, Link, Text } from "@radix-ui/themes";
|
||||
import CopyButton from "@/components/CopyButton";
|
||||
|
||||
export type VCardDisplayProps = {
|
||||
vcardArray: VCardArray;
|
||||
};
|
||||
|
||||
type VCardProperty = [string, Record<string, string>, string, string];
|
||||
|
||||
type StructuredName = {
|
||||
family: string;
|
||||
given: string;
|
||||
additional: string;
|
||||
prefix: string;
|
||||
suffix: string;
|
||||
};
|
||||
|
||||
type Address = {
|
||||
street: string;
|
||||
locality: string;
|
||||
region: string;
|
||||
postal: string;
|
||||
country: string;
|
||||
};
|
||||
|
||||
type ParsedVCard = {
|
||||
fn?: string;
|
||||
name?: StructuredName;
|
||||
org?: string;
|
||||
emails?: string[];
|
||||
phones?: string[];
|
||||
addresses?: Address[];
|
||||
urls?: string[];
|
||||
title?: string;
|
||||
role?: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Parses a vCard (jCard) array and extracts common contact properties.
|
||||
* jCard format: ["vcard", [[prop_name, params, value_type, value], ...]]
|
||||
*/
|
||||
const parseVCard = (vcardArray: VCardArray): ParsedVCard => {
|
||||
const [, properties] = vcardArray;
|
||||
const result: ParsedVCard = {};
|
||||
|
||||
properties.forEach((prop: VCardProperty) => {
|
||||
if (!Array.isArray(prop) || prop.length < 4) return;
|
||||
|
||||
const [name, , , value] = prop as VCardProperty;
|
||||
const nameLower = name.toLowerCase();
|
||||
|
||||
switch (nameLower) {
|
||||
case "fn": // Formatted name
|
||||
result.fn = value;
|
||||
break;
|
||||
case "n": // Structured name [family, given, additional, prefix, suffix]
|
||||
if (Array.isArray(value)) {
|
||||
result.name = {
|
||||
family: value[0],
|
||||
given: value[1],
|
||||
additional: value[2],
|
||||
prefix: value[3],
|
||||
suffix: value[4],
|
||||
};
|
||||
}
|
||||
break;
|
||||
case "org": // Organization
|
||||
result.org = Array.isArray(value) ? value.join(" > ") : value;
|
||||
break;
|
||||
case "email":
|
||||
if (!result.emails) result.emails = [];
|
||||
result.emails.push(value);
|
||||
break;
|
||||
case "tel": // Telephone
|
||||
if (!result.phones) result.phones = [];
|
||||
result.phones.push(value);
|
||||
break;
|
||||
case "adr": // Address [PO, extended, street, locality, region, postal, country]
|
||||
if (Array.isArray(value)) {
|
||||
const address = {
|
||||
street: value[2],
|
||||
locality: value[3],
|
||||
region: value[4],
|
||||
postal: value[5],
|
||||
country: value[6],
|
||||
};
|
||||
if (!result.addresses) result.addresses = [];
|
||||
result.addresses.push(address);
|
||||
}
|
||||
break;
|
||||
case "url":
|
||||
if (!result.urls) result.urls = [];
|
||||
result.urls.push(value);
|
||||
break;
|
||||
case "title":
|
||||
result.title = value;
|
||||
break;
|
||||
case "role":
|
||||
result.role = value;
|
||||
break;
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
};
|
||||
|
||||
const VCardDisplay: FunctionComponent<VCardDisplayProps> = ({ vcardArray }) => {
|
||||
const vcard = parseVCard(vcardArray);
|
||||
|
||||
return (
|
||||
<Table.Root size="2" variant="surface">
|
||||
<Table.Body>
|
||||
{vcard.fn && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Name</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex gap="2" align="center">
|
||||
<Text>{vcard.fn}</Text>
|
||||
<CopyButton value={vcard.fn} />
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.org && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Organization</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex align="center" gap="2">
|
||||
<Code variant="ghost">{vcard.org}</Code>
|
||||
<CopyButton value={vcard.org} />
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.title && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Title</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Text>{vcard.title}</Text>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.role && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Role</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Text>{vcard.role}</Text>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.emails && vcard.emails.length > 0 && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Email</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex direction="column" gap="1">
|
||||
{vcard.emails.map((email: string, index: number) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Link href={`mailto:${email}`}>
|
||||
<Code variant="ghost">{email}</Code>
|
||||
</Link>
|
||||
<CopyButton value={email} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.phones && vcard.phones.length > 0 && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Phone</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex direction="column" gap="1">
|
||||
{vcard.phones.map((phone: string, index: number) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Link href={`tel:${phone}`}>
|
||||
<Code variant="ghost">{phone}</Code>
|
||||
</Link>
|
||||
<CopyButton value={phone} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.addresses && vcard.addresses.length > 0 && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>Address</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex direction="column" gap="2">
|
||||
{vcard.addresses.map((addr: Address, index: number) => (
|
||||
<Text key={index} size="2">
|
||||
{[
|
||||
addr.street,
|
||||
addr.locality,
|
||||
addr.region,
|
||||
addr.postal,
|
||||
addr.country,
|
||||
]
|
||||
.filter(Boolean)
|
||||
.join(", ")}
|
||||
</Text>
|
||||
))}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
|
||||
{vcard.urls && vcard.urls.length > 0 && (
|
||||
<Table.Row>
|
||||
<Table.RowHeaderCell>URL</Table.RowHeaderCell>
|
||||
<Table.Cell>
|
||||
<Flex direction="column" gap="1">
|
||||
{vcard.urls.map((url: string, index: number) => (
|
||||
<Flex key={index} align="center" gap="2">
|
||||
<Link href={url} target="_blank" rel="noreferrer">
|
||||
<Code variant="ghost">{url}</Code>
|
||||
</Link>
|
||||
<CopyButton value={url} />
|
||||
</Flex>
|
||||
))}
|
||||
</Flex>
|
||||
</Table.Cell>
|
||||
</Table.Row>
|
||||
)}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
);
|
||||
};
|
||||
|
||||
export default VCardDisplay;
|
||||
@@ -1,5 +1,5 @@
|
||||
import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { getType } from "@/rdap/utils";
|
||||
import { getType, validateInputForType } from "@/rdap/utils";
|
||||
import type { AutonomousNumber, Domain, IpNetwork, SubmitProps, TargetType } from "@/rdap/schemas";
|
||||
import {
|
||||
AutonomousNumberSchema,
|
||||
@@ -75,35 +75,56 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
||||
if (target == null || target.length == 0)
|
||||
return Result.err(new Error("A target must be given in order to execute a lookup."));
|
||||
|
||||
const targetType = await getTypeEasy(target);
|
||||
let targetType: TargetType;
|
||||
|
||||
if (targetType.isErr) {
|
||||
return Result.err(
|
||||
new Error("Unable to determine type, unable to send query", {
|
||||
cause: targetType.error,
|
||||
})
|
||||
);
|
||||
if (currentType != null) {
|
||||
// User has explicitly selected a type
|
||||
targetType = currentType;
|
||||
|
||||
// Validate the input matches the selected type
|
||||
const validation = await validateInputForType(target, currentType, getRegistry);
|
||||
if (validation.isErr) {
|
||||
// Show warning but proceed with user's selection
|
||||
if (warningHandler != undefined) {
|
||||
warningHandler({
|
||||
message: `Warning: ${validation.error}. Proceeding with selected type "${currentType}".`,
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Autodetect mode
|
||||
const detectedType = await getTypeEasy(target);
|
||||
|
||||
if (detectedType.isErr) {
|
||||
return Result.err(
|
||||
new Error("Unable to determine type, unable to send query", {
|
||||
cause: detectedType.error,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
targetType = detectedType.value;
|
||||
}
|
||||
|
||||
switch (targetType.value) {
|
||||
switch (targetType) {
|
||||
// Block scoped case to allow url const reuse
|
||||
case "ip4": {
|
||||
await loadBootstrap("ip4");
|
||||
const url = getRegistryURL(targetType.value, target);
|
||||
const url = getRegistryURL(targetType, target);
|
||||
const result = await getAndParse<IpNetwork>(url, IpNetworkSchema);
|
||||
if (result.isErr) return Result.err(result.error);
|
||||
return Result.ok({ data: result.value, url });
|
||||
}
|
||||
case "ip6": {
|
||||
await loadBootstrap("ip6");
|
||||
const url = getRegistryURL(targetType.value, target);
|
||||
const url = getRegistryURL(targetType, target);
|
||||
const result = await getAndParse<IpNetwork>(url, IpNetworkSchema);
|
||||
if (result.isErr) return Result.err(result.error);
|
||||
return Result.ok({ data: result.value, url });
|
||||
}
|
||||
case "domain": {
|
||||
await loadBootstrap("domain");
|
||||
const url = getRegistryURL(targetType.value, target);
|
||||
const url = getRegistryURL(targetType, target);
|
||||
|
||||
// HTTP
|
||||
if (url.startsWith("http://") && url != repeatableRef.current) {
|
||||
@@ -123,7 +144,7 @@ const useLookup = (warningHandler?: WarningHandler) => {
|
||||
}
|
||||
case "autnum": {
|
||||
await loadBootstrap("autnum");
|
||||
const url = getRegistryURL(targetType.value, target);
|
||||
const url = getRegistryURL(targetType, target);
|
||||
const result = await getAndParse<AutonomousNumber>(url, AutonomousNumberSchema);
|
||||
if (result.isErr) return Result.err(result.error);
|
||||
return Result.ok({ data: result.value, url });
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// ============================================================================
|
||||
// Enums
|
||||
// ============================================================================
|
||||
|
||||
export const TargetTypeEnum = z.enum([
|
||||
"autnum",
|
||||
"domain",
|
||||
@@ -55,10 +51,6 @@ export const StatusEnum = z.enum([
|
||||
"transfer period",
|
||||
]);
|
||||
|
||||
// ============================================================================
|
||||
// Schemas
|
||||
// ============================================================================
|
||||
|
||||
export const LinkSchema = z.object({
|
||||
value: z.string().optional(), // de-facto optional
|
||||
rel: z.string().optional(), // de-facto optional
|
||||
@@ -69,25 +61,6 @@ export const LinkSchema = z.object({
|
||||
type: z.string().optional(),
|
||||
});
|
||||
|
||||
export const EntitySchema = z.object({
|
||||
objectClassName: z.literal("entity"),
|
||||
handle: z.string().optional(),
|
||||
roles: z.array(z.string()),
|
||||
publicIds: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
identifier: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const NameserverSchema = z.object({
|
||||
objectClassName: z.literal("nameserver"),
|
||||
ldhName: z.string(),
|
||||
});
|
||||
|
||||
export const EventSchema = z.object({
|
||||
eventAction: z.string(),
|
||||
eventActor: z.string().optional(),
|
||||
@@ -97,25 +70,116 @@ export const EventSchema = z.object({
|
||||
export const NoticeSchema = z.object({
|
||||
description: z.string().array(), // de jure required
|
||||
title: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
});
|
||||
|
||||
export const RemarkSchema = z.object({
|
||||
description: z.string().array(), // de jure required
|
||||
title: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
});
|
||||
|
||||
// vCard 4.0 in jCard format (RFC 7095)
|
||||
// Simplified schema - full vCard is complex, so we use a loose schema
|
||||
// Format: ["vcard", [properties...]]
|
||||
export const VCardArraySchema = z.array(z.any());
|
||||
|
||||
export const IPAddressesSchema = z.object({
|
||||
v4: z.array(z.string()).optional(),
|
||||
v6: z.array(z.string()).optional(),
|
||||
});
|
||||
|
||||
export const DSDataSchema = z.object({
|
||||
keyTag: z.number(),
|
||||
algorithm: z.number(),
|
||||
digest: z.string(),
|
||||
digestType: z.number(),
|
||||
});
|
||||
|
||||
export const KeyDataSchema = z.object({
|
||||
flags: z.number(),
|
||||
protocol: z.number(),
|
||||
publicKey: z.string(),
|
||||
algorithm: z.number(),
|
||||
});
|
||||
|
||||
export const SecureDNSSchema = z.object({
|
||||
zoneSigned: z.boolean().optional(),
|
||||
delegationSigned: z.boolean().optional(),
|
||||
maxSigLife: z.number().optional(),
|
||||
dsData: z.array(DSDataSchema).optional(),
|
||||
keyData: z.array(KeyDataSchema).optional(),
|
||||
});
|
||||
|
||||
export const VariantSchema = z.object({
|
||||
relation: z.array(z.string()).optional(),
|
||||
idnTable: z.string().optional(),
|
||||
variantNames: z
|
||||
.array(
|
||||
z.object({
|
||||
ldhName: z.string(),
|
||||
unicodeName: z.string().optional(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
export const NameserverSchema = z.object({
|
||||
objectClassName: z.literal("nameserver"),
|
||||
ldhName: z.string(),
|
||||
unicodeName: z.string().optional(),
|
||||
handle: z.string().optional(),
|
||||
ipAddresses: IPAddressesSchema.optional(),
|
||||
status: z.array(StatusEnum).optional(),
|
||||
events: z.array(EventSchema).optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
remarks: z.array(RemarkSchema).optional(),
|
||||
port43: z.string().optional(),
|
||||
entities: z.lazy(() => z.array(EntitySchema)).optional(),
|
||||
});
|
||||
|
||||
export const IpNetworkSchema = z.object({
|
||||
objectClassName: z.literal("ip network"),
|
||||
handle: z.string(),
|
||||
startAddress: z.string(),
|
||||
endAddress: z.string(),
|
||||
ipVersion: z.enum(["v4", "v6"]),
|
||||
name: z.string(),
|
||||
type: z.string(),
|
||||
name: z.string().optional(),
|
||||
type: z.string().optional(),
|
||||
country: z.string().optional(),
|
||||
parentHandle: z.string().optional(),
|
||||
status: z.array(StatusEnum),
|
||||
entities: z.array(EntitySchema).optional(),
|
||||
remarks: z.any().optional(),
|
||||
links: z.any().optional(),
|
||||
port43: z.any().optional(),
|
||||
events: z.array(EventSchema),
|
||||
status: z.array(StatusEnum).optional(),
|
||||
remarks: z.array(RemarkSchema).optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
port43: z.string().optional(),
|
||||
events: z.array(EventSchema).optional(),
|
||||
// Required for circular reference
|
||||
get entities() {
|
||||
return z.array(EntitySchema).optional();
|
||||
},
|
||||
});
|
||||
|
||||
// Forward declaration for circular Entity reference
|
||||
const BaseEntitySchema = z.object({
|
||||
objectClassName: z.literal("entity"),
|
||||
handle: z.string().optional(),
|
||||
vcardArray: VCardArraySchema.optional(),
|
||||
roles: z.array(z.string()).optional(),
|
||||
publicIds: z
|
||||
.array(
|
||||
z.object({
|
||||
type: z.string(),
|
||||
identifier: z.string(),
|
||||
})
|
||||
)
|
||||
.optional(),
|
||||
status: z.array(StatusEnum).optional(),
|
||||
events: z.array(EventSchema).optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
remarks: z.array(RemarkSchema).optional(),
|
||||
port43: z.string().optional(),
|
||||
});
|
||||
|
||||
export const AutonomousNumberSchema = z.object({
|
||||
@@ -128,24 +192,40 @@ export const AutonomousNumberSchema = z.object({
|
||||
status: z.array(StatusEnum),
|
||||
country: z.string().length(2),
|
||||
events: z.array(EventSchema),
|
||||
entities: z.array(EntitySchema),
|
||||
roles: z.array(z.string()),
|
||||
links: z.array(LinkSchema),
|
||||
get entities() {
|
||||
return z.array(EntitySchema).optional();
|
||||
},
|
||||
links: z.array(LinkSchema).optional(),
|
||||
remarks: z.array(RemarkSchema).optional(),
|
||||
port43: z.string().optional(),
|
||||
});
|
||||
|
||||
// Full Entity schema with circular references
|
||||
export const EntitySchema = BaseEntitySchema.extend({
|
||||
networks: z.lazy(() => z.array(IpNetworkSchema)).optional(),
|
||||
autnums: z.lazy(() => z.array(AutonomousNumberSchema)).optional(),
|
||||
asEventActor: z.array(EventSchema).optional(),
|
||||
get entities() {
|
||||
return z.array(EntitySchema).optional();
|
||||
},
|
||||
});
|
||||
|
||||
export const DomainSchema = z.object({
|
||||
objectClassName: z.literal("domain"),
|
||||
handle: z.string(),
|
||||
handle: z.string().optional(),
|
||||
ldhName: z.string(),
|
||||
unicodeName: z.string().optional(),
|
||||
variants: z.array(VariantSchema).optional(),
|
||||
links: z.array(LinkSchema).optional(),
|
||||
status: z.array(StatusEnum),
|
||||
entities: z.array(EntitySchema),
|
||||
events: z.array(EventSchema),
|
||||
secureDNS: z.any(), // TODO: Complete schema
|
||||
nameservers: z.array(NameserverSchema),
|
||||
rdapConformance: z.string().array(), // TODO: Complete
|
||||
notices: z.array(NoticeSchema),
|
||||
status: z.array(StatusEnum).optional(),
|
||||
entities: z.lazy(() => z.array(EntitySchema)).optional(),
|
||||
events: z.array(EventSchema).optional(),
|
||||
secureDNS: SecureDNSSchema.optional(),
|
||||
nameservers: z.array(NameserverSchema).optional(),
|
||||
rdapConformance: z.string().array().optional(),
|
||||
notices: z.array(NoticeSchema).optional(),
|
||||
remarks: z.array(RemarkSchema).optional(),
|
||||
port43: z.string().optional(),
|
||||
network: IpNetworkSchema.optional(),
|
||||
});
|
||||
|
||||
@@ -166,10 +246,6 @@ export const RegisterSchema = z.object({
|
||||
version: z.string(),
|
||||
});
|
||||
|
||||
// ============================================================================
|
||||
// TypeScript Types
|
||||
// ============================================================================
|
||||
|
||||
// All precise target types that can be placed in the search bar.
|
||||
export type TargetType = z.infer<typeof TargetTypeEnum>;
|
||||
|
||||
@@ -185,6 +261,13 @@ export type Entity = z.infer<typeof EntitySchema>;
|
||||
export type Nameserver = z.infer<typeof NameserverSchema>;
|
||||
export type Event = z.infer<typeof EventSchema>;
|
||||
export type Notice = z.infer<typeof NoticeSchema>;
|
||||
export type Remark = z.infer<typeof RemarkSchema>;
|
||||
export type VCardArray = z.infer<typeof VCardArraySchema>;
|
||||
export type IPAddresses = z.infer<typeof IPAddressesSchema>;
|
||||
export type DSData = z.infer<typeof DSDataSchema>;
|
||||
export type KeyData = z.infer<typeof KeyDataSchema>;
|
||||
export type SecureDNS = z.infer<typeof SecureDNSSchema>;
|
||||
export type Variant = z.infer<typeof VariantSchema>;
|
||||
export type IpNetwork = z.infer<typeof IpNetworkSchema>;
|
||||
export type AutonomousNumber = z.infer<typeof AutonomousNumberSchema>;
|
||||
export type Register = z.infer<typeof RegisterSchema>;
|
||||
|
||||
@@ -176,3 +176,34 @@ export async function getType(
|
||||
|
||||
return Result.err(new Error("No patterns matched the input"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates if a given input matches a specific target type.
|
||||
* Used to warn users when their explicit type selection doesn't match the input format.
|
||||
*
|
||||
* @param value - The input value to validate
|
||||
* @param type - The expected target type
|
||||
* @param getRegistry - Function to fetch registry data
|
||||
* @returns A Result containing true if valid, or an error message if invalid
|
||||
*/
|
||||
export async function validateInputForType(
|
||||
value: string,
|
||||
type: TargetType,
|
||||
getRegistry: (type: RootRegistryType) => Promise<Register>
|
||||
): Promise<Result<true, string>> {
|
||||
const validator = TypeValidators.get(type);
|
||||
if (!validator) {
|
||||
return Result.err(`Unknown type: ${type}`);
|
||||
}
|
||||
|
||||
const result = await validator({ value, getRegistry });
|
||||
|
||||
if (result === true) {
|
||||
return Result.ok(true);
|
||||
} else if (result === false) {
|
||||
return Result.err(`Input "${value}" does not match the format for type "${type}"`);
|
||||
} else {
|
||||
// result is an error message string
|
||||
return Result.err(result);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user