diff --git a/web/ui/react-app/src/pages/targets/EndpointLink.test.tsx b/web/ui/react-app/src/pages/targets/EndpointLink.test.tsx index 6ec682942..9133219f6 100644 --- a/web/ui/react-app/src/pages/targets/EndpointLink.test.tsx +++ b/web/ui/react-app/src/pages/targets/EndpointLink.test.tsx @@ -1,6 +1,6 @@ import React from 'react'; import { shallow, mount } from 'enzyme'; -import { Badge, Alert } from 'reactstrap'; +import { Badge } from 'reactstrap'; import EndpointLink from './EndpointLink'; describe('EndpointLink', () => { @@ -29,11 +29,24 @@ describe('EndpointLink', () => { const targetLabel = badges.filterWhere((badge) => badge.children().text() === 'target="http://some-service"'); expect(targetLabel.length).toEqual(1); }); - - it('renders an alert if url is invalid', () => { - const endpointLink = shallow(); - const err = endpointLink.find(Alert); - expect(err.render().text()).toEqual('Error: Invalid URL: afdsacas'); + // In cases of IPv6 addresses with a Zone ID, URL may not be parseable. + // See https://github.com/prometheus/prometheus/issues/9760 + it('renders an anchor for IPv6 link with zone ID including labels for query params', () => { + const endpoint = + 'http://[fe80::f1ee:adeb:371d:983%eth1]:9100/stats/prometheus?module=http_2xx&target=http://some-service'; + const globalURL = + 'http://[fe80::f1ee:adeb:371d:983%eth1]:9100/stats/prometheus?module=http_2xx&target=http://some-service'; + const endpointLink = shallow(); + const anchor = endpointLink.find('a'); + const badges = endpointLink.find(Badge); + expect(anchor.prop('href')).toEqual(globalURL); + expect(anchor.children().text()).toEqual('http://[fe80::f1ee:adeb:371d:983%eth1]:9100/stats/prometheus'); + expect(endpointLink.find('br')).toHaveLength(1); + expect(badges).toHaveLength(2); + const moduleLabel = badges.filterWhere((badge) => badge.children().text() === 'module="http_2xx"'); + expect(moduleLabel.length).toEqual(1); + const targetLabel = badges.filterWhere((badge) => badge.children().text() === 'target="http://some-service"'); + expect(targetLabel.length).toEqual(1); }); it('handles params with multiple values correctly', () => { diff --git a/web/ui/react-app/src/pages/targets/EndpointLink.tsx b/web/ui/react-app/src/pages/targets/EndpointLink.tsx index 083017225..504fbc337 100644 --- a/web/ui/react-app/src/pages/targets/EndpointLink.tsx +++ b/web/ui/react-app/src/pages/targets/EndpointLink.tsx @@ -1,5 +1,5 @@ import React, { FC } from 'react'; -import { Badge, Alert } from 'reactstrap'; +import { Badge } from 'reactstrap'; export interface EndpointLinkProps { endpoint: string; @@ -8,23 +8,28 @@ export interface EndpointLinkProps { const EndpointLink: FC = ({ endpoint, globalUrl }) => { let url: URL; + let search = ''; + let invalidURL = false; try { url = new URL(endpoint); } catch (err: unknown) { - const error = err as Error; - return ( - - Error: {error.message} - - ); + // In cases of IPv6 addresses with a Zone ID, URL may not be parseable. + // See https://github.com/prometheus/prometheus/issues/9760 + // In this case, we attempt to prepare a synthetic URL with the + // same query parameters, for rendering purposes. + invalidURL = true; + if (endpoint.indexOf('?') > -1) { + search = endpoint.substring(endpoint.indexOf('?')); + } + url = new URL('http://0.0.0.0' + search); } const { host, pathname, protocol, searchParams }: URL = url; const params = Array.from(searchParams.entries()); - + const displayLink = invalidURL ? endpoint.replace(search, '') : `${protocol}//${host}${pathname}`; return ( <> - {`${protocol}//${host}${pathname}`} + {displayLink} {params.length > 0 ?
: null} {params.map(([labelName, labelValue]: [string, string]) => { return (