diff --git a/web/src/OverviewActionBar.tsx b/web/src/OverviewActionBar.tsx index cdd086ace3..334a5ae62e 100644 --- a/web/src/OverviewActionBar.tsx +++ b/web/src/OverviewActionBar.tsx @@ -21,7 +21,7 @@ import { InstrumentedButton, InstrumentedTextField, } from "./instrumentedComponents" -import { displayURL } from "./links" +import { displayURL, resolveURL } from "./links" import LogActions from "./LogActions" import { EMPTY_TERM, @@ -693,7 +693,7 @@ function openEndpointUrl(url: string) { // We deliberately don't use rel=noopener. These are trusted tabs, and we want // to have a persistent link to them (so that clicking on the same link opens // the same tab). - window.open(url, url) + window.open(resolveURL(url), url) } export function OverviewWidgets(props: { buttons?: UIButton[] }) { @@ -736,17 +736,18 @@ export default function OverviewActionBar(props: OverviewActionBarProps) { if (i !== 0) { endpointEls.push() } + let url = resolveURL(ep.url || "") endpointEls.push( void incr("ui.web.endpoint", { action: AnalyticsAction.Click }) } - href={ep.url} + href={url} // We use ep.url as the target, so that clicking the link re-uses the tab. - target={ep.url} - key={ep.url} + target={url} + key={url} > - {ep.name || displayURL(ep)} + {ep.name || displayURL(url)} ) }) diff --git a/web/src/OverviewTableColumns.tsx b/web/src/OverviewTableColumns.tsx index 7739e9c3b8..7b21f65fe7 100644 --- a/web/src/OverviewTableColumns.tsx +++ b/web/src/OverviewTableColumns.tsx @@ -14,7 +14,7 @@ import { InstrumentedButton, InstrumentedCheckbox, } from "./instrumentedComponents" -import { displayURL } from "./links" +import { displayURL, resolveURL } from "./links" import { OverviewButtonMixin } from "./OverviewButton" import { OverviewTableBuildButton } from "./OverviewTableBuildButton" import OverviewTableStarResourceButton from "./OverviewTableStarResourceButton" @@ -505,19 +505,20 @@ export function TableEndpointColumn({ row }: CellProps) { } let endpoints = row.original.endpoints.map((ep: any) => { + let url = resolveURL(ep.url || "") return ( void incr("ui.web.endpoint", { action: AnalyticsAction.Click }) } - href={ep.url} + href={url} // We use ep.url as the target, so that clicking the link re-uses the tab. - target={ep.url} - key={ep.url} + target={url} + key={url} > - - {ep.name || displayURL(ep)} + + {ep.name || displayURL(url)} ) diff --git a/web/src/links.test.tsx b/web/src/links.test.tsx new file mode 100644 index 0000000000..0f05b660cd --- /dev/null +++ b/web/src/links.test.tsx @@ -0,0 +1,31 @@ +import { resolveURL, displayURL } from "./links" + +describe("links", () => { + it("resolves 0.0.0.0", () => { + expect(resolveURL("http://localhost:8000/x")).toEqual( + "http://localhost:8000/x" + ) + expect(resolveURL("http://0.0.0.0:8000/x")).toEqual( + "http://localhost:8000/x" + ) + }) + + it("handles partial urls", () => { + expect(resolveURL("localhost:8000")).toEqual("localhost:8000") + }) + + it("strips schemes", () => { + expect(displayURL("https://localhost:8000")).toEqual("localhost:8000") + expect(displayURL("http://localhost:8000")).toEqual("localhost:8000") + expect(displayURL("http://www.google.com")).toEqual("google.com") + }) + + it("strips trailing slash", () => { + expect(displayURL("http://localhost:8000/")).toEqual("localhost:8000") + expect(displayURL("http://localhost:8000/foo/")).toEqual( + "localhost:8000/foo/" + ) + expect(displayURL("http://localhost/")).toEqual("localhost") + expect(displayURL("http://localhost/foo/")).toEqual("localhost/foo/") + }) +}) diff --git a/web/src/links.ts b/web/src/links.ts index cabf7f310f..3f7f081137 100644 --- a/web/src/links.ts +++ b/web/src/links.ts @@ -1,10 +1,26 @@ // Helper functions for displaying links -import { UILink } from "./types" +// If a URL contains the hostname 0.0.0.0, resolve that to the current +// window.location.hostname. This ensures that tilt sends users to the +// right place when it's being accessed from a remote machine. +export function resolveURL(input: string): string { + if (!input) { + return input + } + try { + let url = new URL(input) + if (url.hostname === "0.0.0.0") { + url.hostname = window.location.hostname + return url.toString() + } + } catch (err) {} // fall through + return input +} -export function displayURL(li: UILink): string { - let url = li.url?.replace(/^(http:\/\/)/, "") - url = url?.replace(/^(https:\/\/)/, "") - url = url?.replace(/^(www\.)/, "") +export function displayURL(url: string): string { + url = url.replace(/^http:\/\//, "") + url = url.replace(/^https:\/\//, "") + url = url.replace(/^www\./, "") + url = url.replace(/^([^\/]+)\/$/, "$1") return url || "" }