temporalio / ui

Temporal UI
https://docs.temporal.io/web-ui
MIT License
193 stars 70 forks source link

Support serving UI under subdomain #2255

Open gregbrowndev opened 3 months ago

gregbrowndev commented 3 months ago

Describe the bug

Link to Slack thread where I originally asked this question.

I want to host the Temporal UI at the root path of a subdomain, e.g. temporal.[mydomain].com, using host-based routing in ALB.

This is important because I can't avoid subdomains in the URL, even if I hosted the UI at a specific path, e.g. [mydomain].com/temporal, since I also have a staging environment where the UI will be hosted at staging.[mydomain].com/temporal.

When I configure host-based routing in ALB to route temporal.[mydomain].com to the frontend service, I have two options to configure the frontend service.

  1. With TEMPORAL_UI_PUBLIC_PATH=/, the page fails to fetch the JS resources in the browser because the HTML returned from the server contains // in front of the JS bundles, e.g.

    <link rel="modulepreload" href="//_app/immutable/entry/start.XNU22-I9.js"> 

    This tells the browser to ignore the same origin rule, and the requests instead get sent to https://_app/immutable/entry/start.XNU22-I9.js which doesn't exist.

  2. With TEMPORAL_UI_PUBLIC_PATH=temporal.[mydomain].com, now the UI can fetch the bundles linked in the HTML, but then fails to load the JS.

    E.g. this file temporal.[mydomain].com]/_app/immutable/entry/start.XNU22-I9.js contains:

    import{c as a}from"../chunks/entry.npYmAy9v.js";export{a as start};

    The referenced file does exist and contains a load of JS, if I go to it directly, i.e. temporal.[mydomain].com/_app/immutable/chunks/entry.npYmAy9v.js. But it fails to load in the browser:

    (index):26 Uncaught (in promise) TypeError: Failed to resolve module specifier 'temporal.[mydomain].com/_app/immutable/entry/start.XNU22-I9.js'
      at (index):26:14

To Reproduce Steps to reproduce the behavior:

  1. Host Temporal Frontend service (I've got it on ECS) with TEMPORAL_UI_PUBLIC_PATH=/

  2. Configure load balancer to forward requests matching the host and path, e.g. with ALB listener rule:

    condition {
      host_header {
        values = ["temporal.${local.domain_name}"]
      }
    }
    
    condition {
      path_pattern {
        values = [
          "/*",
        ]
      }
    }
  3. Visit Temporal UI in the browser, e.g. https://temporal.[mydomain].com

Expected behavior

The frontend SPA should respect the same origin rule and fetch resources from TEMPORAL_UI_PUBLIC_PATH on the same domain (including subdomains) it originated.

Screenshots

N/a

Desktop (please complete the following information):

Additional context

N/A

gregbrowndev commented 3 months ago

OK I've figured it out. You can deploy on a subdomain, but setting TEMPORAL_UI_PUBLIC_PATH=/ completely breaks the UI. Leaving the env var unset fixes the issue.

The reason this breaks the deployment is because setting the TEMPORAL_UI_PUBLIC_PATH=/ causes the HTML to render the link tags in the HTML meta with an extra /.

The ui-server project contains the links in question that fetch the JS bundles. If you set the public path, what actually gets rendered is:

        <link rel="modulepreload" href="//_app/immutable/entry/start.PkAQ5dFz.js">
        <link rel="modulepreload" href="//_app/immutable/chunks/entry.wLcqREKA.js">
        <link rel="modulepreload" href="//_app/immutable/chunks/scheduler.1T9hOnFr.js">
        <link rel="modulepreload" href="//_app/immutable/chunks/paths.IPjQg0PU.js">
        <link rel="modulepreload" href="//_app/immutable/chunks/control.pJ1mnnAb.js">
        <link rel="modulepreload" href="//_app/immutable/entry/app.2gko0slu.js">
        <link rel="modulepreload" href="//_app/immutable/chunks/index.cVovwi_s.js">

Which tells the browser to ignore the same origin hostname completely and fetch the resources as:

        <link rel="modulepreload" href="https://_app/immutable/entry/start.PkAQ5dFz.js">
        <link rel="modulepreload" href="https://_app/immutable/chunks/entry.wLcqREKA.js">
        ...

So setting the public path when you're using subdomains should be avoided. This tripped me up for several hours today, as I would expect a public path of "/" to mean the root path of the same origin.