Show ANAME for apex domains
This commit is contained in:
parent
5ca8eeff51
commit
95b92a07f1
7 changed files with 45 additions and 24 deletions
|
|
@ -1519,8 +1519,9 @@ space carries my branding.
|
||||||
Acceptance:
|
Acceptance:
|
||||||
|
|
||||||
- Saving a domain under "Manage" shows it on the relay card with a "Pending"
|
- Saving a domain under "Manage" shows it on the relay card with a "Pending"
|
||||||
badge and the CNAME record to configure, whose copy button puts the target on
|
badge and the DNS record to configure, whose copy button puts the target on
|
||||||
the clipboard.
|
the clipboard. A subdomain gets a CNAME; a bare domain, which DNS won't let
|
||||||
|
take one, gets an ALIAS.
|
||||||
- "Verify DNS record" flips the badge to "Verified" once the backend reports it.
|
- "Verify DNS record" flips the badge to "Verified" once the backend reports it.
|
||||||
- The relay's displayed address then switches to the custom domain.
|
- The relay's displayed address then switches to the custom domain.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -931,15 +931,23 @@ test("US-101 point a custom domain at a hosted relay", async ({seed, as}) => {
|
||||||
|
|
||||||
const domainForm = dialog(page, "Custom domain")
|
const domainForm = dialog(page, "Custom domain")
|
||||||
|
|
||||||
await domainForm.getByPlaceholder("relay.example.com").fill("other.test")
|
await domainForm.getByPlaceholder("relay.example.com").fill("relay.other.test")
|
||||||
await domainForm.getByRole("button", {name: "Save", exact: true}).click()
|
await domainForm.getByRole("button", {name: "Save", exact: true}).click()
|
||||||
|
|
||||||
await expect(page.getByRole("alert")).toContainText("Custom domain saved.")
|
await expect(page.getByRole("alert")).toContainText("Custom domain saved.")
|
||||||
await expect(page.getByText("other.test", {exact: true})).toBeVisible()
|
await expect(page.getByText("relay.other.test", {exact: true})).toBeVisible()
|
||||||
await expect(page.getByText("Pending", {exact: true})).toBeVisible()
|
await expect(page.getByText("Pending", {exact: true})).toBeVisible()
|
||||||
await expect(page.getByText("other.test CNAME space.test")).toBeVisible()
|
await expect(page.getByText("relay.other.test CNAME space.test")).toBeVisible()
|
||||||
|
|
||||||
await page.locator('[data-tip="Copy CNAME target"]').click()
|
// A bare domain can't take a CNAME, so it's shown as an ALIAS at the same target
|
||||||
|
await page.getByRole("button", {name: "Manage"}).click()
|
||||||
|
await domainForm.getByPlaceholder("relay.example.com").fill("other.test")
|
||||||
|
await domainForm.getByRole("button", {name: "Save", exact: true}).click()
|
||||||
|
|
||||||
|
await expect(page.getByText("other.test", {exact: true})).toBeVisible()
|
||||||
|
await expect(page.getByText("other.test ALIAS space.test")).toBeVisible()
|
||||||
|
|
||||||
|
await page.locator('[data-tip="Copy ALIAS target"]').click()
|
||||||
|
|
||||||
await expect(page.getByRole("alert")).toContainText("Copied to clipboard!")
|
await expect(page.getByRole("alert")).toContainText("Copied to clipboard!")
|
||||||
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("space.test")
|
expect(await page.evaluate(() => navigator.clipboard.readText())).toBe("space.test")
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import {Profiles} from "@welshman/app"
|
import {Profiles} from "@welshman/app"
|
||||||
|
import Compass from "@assets/icons/compass.svg?dataurl"
|
||||||
import Letter from "@assets/icons/letter.svg?dataurl"
|
import Letter from "@assets/icons/letter.svg?dataurl"
|
||||||
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
import Magnifier from "@assets/icons/magnifier.svg?dataurl"
|
||||||
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
import UserRounded from "@assets/icons/user-rounded.svg?dataurl"
|
||||||
|
|
@ -67,6 +68,11 @@
|
||||||
<ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} />
|
<ImageIcon alt="Settings" src={UserRounded} class="rounded-full" size={8} />
|
||||||
{/if}
|
{/if}
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
|
{#if PLATFORM_RELAYS.length === 0}
|
||||||
|
<PrimaryNavItem href="/spaces" title="All Spaces" prefix="no-highlight">
|
||||||
|
<ImageIcon alt="All Spaces" src={Compass} size={8} />
|
||||||
|
</PrimaryNavItem>
|
||||||
|
{/if}
|
||||||
<PrimaryNavItem
|
<PrimaryNavItem
|
||||||
title="Messages"
|
title="Messages"
|
||||||
onclick={chatHandler}
|
onclick={chatHandler}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import cx from "classnames"
|
import cx from "classnames"
|
||||||
import Widget from "@assets/icons/widget-4.svg?dataurl"
|
|
||||||
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
import ImageIcon from "@lib/components/ImageIcon.svelte"
|
||||||
import Divider from "@lib/components/Divider.svelte"
|
import Divider from "@lib/components/Divider.svelte"
|
||||||
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
import PrimaryNavItem from "@lib/components/PrimaryNavItem.svelte"
|
||||||
|
|
@ -46,11 +45,11 @@
|
||||||
<PrimaryNavItem title="Home" href="/home">
|
<PrimaryNavItem title="Home" href="/home">
|
||||||
<ImageIcon alt="Home" src={PLATFORM_LOGO} class="rounded-full" size={10} />
|
<ImageIcon alt="Home" src={PLATFORM_LOGO} class="rounded-full" size={10} />
|
||||||
</PrimaryNavItem>
|
</PrimaryNavItem>
|
||||||
<Divider />
|
<Divider class="w-full" />
|
||||||
<div
|
<div
|
||||||
bind:this={element}
|
bind:this={element}
|
||||||
onscroll={measure}
|
onscroll={measure}
|
||||||
class="primary-nav__spaces"
|
class="primary-nav__spaces mb-2"
|
||||||
style:--fade-top="{Math.min(hiddenAbove, fadeSize)}px"
|
style:--fade-top="{Math.min(hiddenAbove, fadeSize)}px"
|
||||||
style:--fade-bottom="{Math.min(hiddenBelow, fadeSize)}px">
|
style:--fade-bottom="{Math.min(hiddenBelow, fadeSize)}px">
|
||||||
<DragList
|
<DragList
|
||||||
|
|
@ -62,8 +61,5 @@
|
||||||
{/snippet}
|
{/snippet}
|
||||||
</DragList>
|
</DragList>
|
||||||
</div>
|
</div>
|
||||||
<PrimaryNavItem href="/spaces" title="All Spaces" prefix="no-highlight">
|
|
||||||
<ImageIcon alt="All Spaces" src={Widget} size={8} />
|
|
||||||
</PrimaryNavItem>
|
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,11 @@
|
||||||
const host = $derived(relayHost(current))
|
const host = $derived(relayHost(current))
|
||||||
const relayLabel = $derived(current.info_name || current.subdomain)
|
const relayLabel = $derived(current.info_name || current.subdomain)
|
||||||
const domainVerified = $derived(flagToBool(current.custom_domain_verified, false))
|
const domainVerified = $derived(flagToBool(current.custom_domain_verified, false))
|
||||||
const cnameTarget = $derived(canonicalRelayHost(current))
|
const recordTarget = $derived(canonicalRelayHost(current))
|
||||||
|
// DNS forbids a CNAME at a zone apex, so those domains point at the same
|
||||||
|
// target using their provider's ALIAS/ANAME record instead.
|
||||||
|
const isApex = $derived(current.custom_domain.split(".").length === 2)
|
||||||
|
const recordType = $derived(isApex ? "ALIAS" : "CNAME")
|
||||||
const isPaidPlan = $derived(current.plan_id !== "free")
|
const isPaidPlan = $derived(current.plan_id !== "free")
|
||||||
|
|
||||||
// Adding, removing or verifying a custom domain moves the relay's host, so
|
// Adding, removing or verifying a custom domain moves the relay's host, so
|
||||||
|
|
@ -156,7 +160,7 @@
|
||||||
showMenu = false
|
showMenu = false
|
||||||
}
|
}
|
||||||
|
|
||||||
const copyCname = () => clip(cnameTarget)
|
const copyTarget = () => clip(recordTarget)
|
||||||
|
|
||||||
// Verification runs in a backend poller; reload to pick up the result.
|
// Verification runs in a backend poller; reload to pick up the result.
|
||||||
const verify = async () => {
|
const verify = async () => {
|
||||||
|
|
@ -346,18 +350,23 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-2 rounded-xl border border-line bg-surface px-3 py-2">
|
<div class="flex items-center gap-2 rounded-xl border border-line bg-surface px-3 py-2">
|
||||||
<code class="min-w-0 flex-1 break-all font-mono text-xs text-content">
|
<code class="min-w-0 flex-1 break-all font-mono text-xs text-content">
|
||||||
{current.custom_domain} CNAME {cnameTarget}
|
{current.custom_domain}
|
||||||
|
{recordType}
|
||||||
|
{recordTarget}
|
||||||
</code>
|
</code>
|
||||||
<Button
|
<Button
|
||||||
class="button button-neutral button-sm shrink-0"
|
class="button button-neutral button-sm shrink-0"
|
||||||
data-tip="Copy CNAME target"
|
data-tip="Copy {recordType} target"
|
||||||
onclick={copyCname}>
|
onclick={copyTarget}>
|
||||||
<Icon icon={Copy} size={4} />
|
<Icon icon={Copy} size={4} />
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<p class="text-xs text-content-muted">
|
{#if isApex}
|
||||||
For apex domains (e.g. example.com), use an ALIAS or ANAME record instead.
|
<p class="text-xs text-content-muted">
|
||||||
</p>
|
A bare domain can't use a CNAME. Your provider may call this record ANAME, or offer
|
||||||
|
CNAME flattening instead.
|
||||||
|
</p>
|
||||||
|
{/if}
|
||||||
<Button
|
<Button
|
||||||
class="button button-neutral button-sm self-start"
|
class="button button-neutral button-sm self-start"
|
||||||
onclick={verify}
|
onclick={verify}
|
||||||
|
|
|
||||||
|
|
@ -367,7 +367,7 @@ export const relayHost = (relay: HostedRelay): string =>
|
||||||
? relay.custom_domain
|
? relay.custom_domain
|
||||||
: canonicalRelayHost(relay)
|
: canonicalRelayHost(relay)
|
||||||
|
|
||||||
// The host a custom domain must CNAME to.
|
// The host a custom domain's DNS record must point at.
|
||||||
export const canonicalRelayHost = (relay: HostedRelay): string =>
|
export const canonicalRelayHost = (relay: HostedRelay): string =>
|
||||||
`${relay.subdomain}.${relay.zooid_domain}`
|
`${relay.subdomain}.${relay.zooid_domain}`
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,15 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type {Snippet} from "svelte"
|
import type {Snippet} from "svelte"
|
||||||
|
|
||||||
interface Props {
|
type Props = {
|
||||||
children?: Snippet
|
children?: Snippet
|
||||||
|
class?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
const {children}: Props = $props()
|
const {children, class: className}: Props = $props()
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div class="flex items-center gap-2 p-2 text-xs uppercase text-content-subtle">
|
<div class="flex items-center gap-2 p-2 text-xs uppercase text-content-subtle {className}">
|
||||||
<div class="h-px grow" style="background-color: var(--line)"></div>
|
<div class="h-px grow" style="background-color: var(--line)"></div>
|
||||||
{#if children}
|
{#if children}
|
||||||
<p>{@render children?.()}</p>
|
<p>{@render children?.()}</p>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue