forked from mirrors/thatmattlove-hyperglass
fix PeeringDB link rendering
This commit is contained in:
parent
0cbec73061
commit
19bdfe74d0
3 changed files with 42 additions and 38 deletions
|
|
@ -3,9 +3,10 @@
|
||||||
# Standard Library
|
# Standard Library
|
||||||
import typing as t
|
import typing as t
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
import urllib.parse
|
||||||
|
|
||||||
# Third Party
|
# Third Party
|
||||||
from pydantic import Field, ConfigDict, ValidationInfo, field_validator
|
from pydantic import Field, ConfigDict, ValidationInfo, field_validator, HttpUrl
|
||||||
|
|
||||||
# Project
|
# Project
|
||||||
from hyperglass.settings import Settings
|
from hyperglass.settings import Settings
|
||||||
|
|
@ -108,6 +109,17 @@ class Params(ParamsPublic, HyperglassModel):
|
||||||
return [str(f) for f in matching_plugins]
|
return [str(f) for f in matching_plugins]
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@field_validator("web", mode="after")
|
||||||
|
@classmethod
|
||||||
|
def validate_web(cls, web: Web, info: ValidationInfo) -> Web:
|
||||||
|
"""String-format Link URLs."""
|
||||||
|
for link in web.links:
|
||||||
|
url = urllib.parse.unquote(str(link.url), encoding="utf-8", errors="replace").format(
|
||||||
|
primary_asn=info.data.get("primary_asn", "65000")
|
||||||
|
)
|
||||||
|
link.url = HttpUrl(url)
|
||||||
|
return web
|
||||||
|
|
||||||
def common_plugins(self) -> t.Tuple[Path, ...]:
|
def common_plugins(self) -> t.Tuple[Path, ...]:
|
||||||
"""Get all validated external common plugins as Path objects."""
|
"""Get all validated external common plugins as Path objects."""
|
||||||
return tuple(Path(p) for p in self.plugins)
|
return tuple(Path(p) for p in self.plugins)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ import { Flex, HStack, useToken } from '@chakra-ui/react';
|
||||||
import { useMemo } from 'react';
|
import { useMemo } from 'react';
|
||||||
import { useConfig } from '~/context';
|
import { useConfig } from '~/context';
|
||||||
import { DynamicIcon } from '~/elements';
|
import { DynamicIcon } from '~/elements';
|
||||||
import { useBreakpointValue, useColorValue, useMobile, useStrf } from '~/hooks';
|
import { useBreakpointValue, useColorValue, useMobile } from '~/hooks';
|
||||||
import { isLink, isMenu } from '~/types';
|
import { isLink, isMenu } from '~/types';
|
||||||
import { FooterButton } from './button';
|
import { FooterButton } from './button';
|
||||||
import { ColorModeToggle } from './color-mode';
|
import { ColorModeToggle } from './color-mode';
|
||||||
|
|
@ -11,7 +11,9 @@ import { FooterLink } from './link';
|
||||||
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
import type { ButtonProps, LinkProps } from '@chakra-ui/react';
|
||||||
import type { Link, Menu } from '~/types';
|
import type { Link, Menu } from '~/types';
|
||||||
|
|
||||||
function buildItems(links: Link[], menus: Menu[]): [(Link | Menu)[], (Link | Menu)[]] {
|
type MenuItems = (Link | Menu)[];
|
||||||
|
|
||||||
|
function buildItems(links: Link[], menus: Menu[]): [MenuItems, MenuItems] {
|
||||||
const leftLinks = links.filter(link => link.side === 'left');
|
const leftLinks = links.filter(link => link.side === 'left');
|
||||||
const leftMenus = menus.filter(menu => menu.side === 'left');
|
const leftMenus = menus.filter(menu => menu.side === 'left');
|
||||||
const rightLinks = links.filter(link => link.side === 'right');
|
const rightLinks = links.filter(link => link.side === 'right');
|
||||||
|
|
@ -22,8 +24,23 @@ function buildItems(links: Link[], menus: Menu[]): [(Link | Menu)[], (Link | Men
|
||||||
return [left, right];
|
return [left, right];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const LinkOnSide = (props: { item: ArrayElement<MenuItems>; side: 'left' | 'right' }) => {
|
||||||
|
const { item, side } = props;
|
||||||
|
if (isLink(item)) {
|
||||||
|
const icon: Partial<ButtonProps & LinkProps> = {};
|
||||||
|
|
||||||
|
if (item.showIcon) {
|
||||||
|
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
||||||
|
}
|
||||||
|
return <FooterLink key={item.title} href={item.url} title={item.title} {...icon} />;
|
||||||
|
}
|
||||||
|
if (isMenu(item)) {
|
||||||
|
return <FooterButton key={item.title} side={side} content={item.content} title={item.title} />;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
export const Footer = (): JSX.Element => {
|
export const Footer = (): JSX.Element => {
|
||||||
const { web, content, primaryAsn } = useConfig();
|
const { web, content } = useConfig();
|
||||||
|
|
||||||
const footerBg = useColorValue('blackAlpha.50', 'whiteAlpha.100');
|
const footerBg = useColorValue('blackAlpha.50', 'whiteAlpha.100');
|
||||||
const footerColor = useColorValue('black', 'white');
|
const footerColor = useColorValue('black', 'white');
|
||||||
|
|
@ -34,8 +51,6 @@ export const Footer = (): JSX.Element => {
|
||||||
|
|
||||||
const [left, right] = useMemo(() => buildItems(web.links, web.menus), [web.links, web.menus]);
|
const [left, right] = useMemo(() => buildItems(web.links, web.menus), [web.links, web.menus]);
|
||||||
|
|
||||||
const strF = useStrf();
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HStack
|
<HStack
|
||||||
px={6}
|
px={6}
|
||||||
|
|
@ -51,39 +66,13 @@ export const Footer = (): JSX.Element => {
|
||||||
overflowY={{ base: 'auto', lg: 'unset' }}
|
overflowY={{ base: 'auto', lg: 'unset' }}
|
||||||
justifyContent={{ base: 'center', lg: 'space-between' }}
|
justifyContent={{ base: 'center', lg: 'space-between' }}
|
||||||
>
|
>
|
||||||
{left.map(item => {
|
{left.map(item => (
|
||||||
if (isLink(item)) {
|
<LinkOnSide key={item.title} item={item} side="left" />
|
||||||
const url = strF(item.url, { primaryAsn }, '/');
|
))}
|
||||||
const icon: Partial<ButtonProps & LinkProps> = {};
|
|
||||||
|
|
||||||
if (item.showIcon) {
|
|
||||||
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
|
||||||
}
|
|
||||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
|
||||||
}
|
|
||||||
if (isMenu(item)) {
|
|
||||||
return (
|
|
||||||
<FooterButton key={item.title} side="left" content={item.content} title={item.title} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
{!isMobile && <Flex p={0} flex="1 0 auto" maxWidth="100%" mr="auto" />}
|
{!isMobile && <Flex p={0} flex="1 0 auto" maxWidth="100%" mr="auto" />}
|
||||||
{right.map(item => {
|
{right.map(item => (
|
||||||
if (isLink(item)) {
|
<LinkOnSide key={item.title} item={item} side="right" />
|
||||||
const url = strF(item.url, { primaryAsn }, '/');
|
))}
|
||||||
const icon: Partial<ButtonProps & LinkProps> = {};
|
|
||||||
|
|
||||||
if (item.showIcon) {
|
|
||||||
icon.rightIcon = <DynamicIcon icon={{ go: 'GoLinkExternal' }} />;
|
|
||||||
}
|
|
||||||
return <FooterLink key={item.title} href={url} title={item.title} {...icon} />;
|
|
||||||
}
|
|
||||||
if (isMenu(item)) {
|
|
||||||
return (
|
|
||||||
<FooterButton key={item.title} side="right" content={item.content} title={item.title} />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
{web.credit.enable && (
|
{web.credit.enable && (
|
||||||
<FooterButton
|
<FooterButton
|
||||||
key="credit"
|
key="credit"
|
||||||
|
|
|
||||||
3
hyperglass/ui/types/globals.d.ts
vendored
3
hyperglass/ui/types/globals.d.ts
vendored
|
|
@ -9,6 +9,9 @@ export declare global {
|
||||||
|
|
||||||
type Swap<T, K extends keyof T, V> = Record<K, V> & Omit<T, K>;
|
type Swap<T, K extends keyof T, V> = Record<K, V> & Omit<T, K>;
|
||||||
|
|
||||||
|
type ArrayElement<ArrayType extends readonly unknown[]> =
|
||||||
|
ArrayType extends readonly (infer ElementType)[] ? ElementType : never;
|
||||||
|
|
||||||
type RPKIState = 0 | 1 | 2 | 3;
|
type RPKIState = 0 | 1 | 2 | 3;
|
||||||
|
|
||||||
type ResponseLevel = 'success' | 'warning' | 'error' | 'danger';
|
type ResponseLevel = 'success' | 'warning' | 'error' | 'danger';
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue