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 || ""
}