From d100af0402c23f7810f2e58edf1981355b98652b Mon Sep 17 00:00:00 2001 From: nfebe Date: Fri, 17 Jul 2026 00:03:11 +0100 Subject: [PATCH] feat(ui): Add routing-only hostnames to the domain form A domain can now carry hostnames that route to the deployment but are never issued a certificate, for a hostname whose TLS is terminated by an external proxy such as Cloudflare. They are entered separately from ordinary aliases, which continue to request certificates, so the two cases are not confused. --- src/components/DomainFormModal.test.ts | 59 ++++++++++++++++++++++++++ src/components/DomainFormModal.vue | 38 +++++++++++++++++ src/types/index.ts | 1 + 3 files changed, 98 insertions(+) create mode 100644 src/components/DomainFormModal.test.ts diff --git a/src/components/DomainFormModal.test.ts b/src/components/DomainFormModal.test.ts new file mode 100644 index 0000000..a4b8287 --- /dev/null +++ b/src/components/DomainFormModal.test.ts @@ -0,0 +1,59 @@ +import { describe, it, expect, afterEach } from "vitest"; +import { mount, type VueWrapper } from "@vue/test-utils"; +import DomainFormModal from "./DomainFormModal.vue"; +import type { DomainConfig } from "@/types"; + +let wrapper: VueWrapper; + +const mountModal = (domain?: DomainConfig) => { + wrapper = mount(DomainFormModal, { + attachTo: document.body, + props: { + visible: true, + domain: domain ?? null, + services: [], + deploymentName: "shop", + }, + }); + return wrapper; +}; + +afterEach(() => { + wrapper?.unmount(); + document.body.innerHTML = ""; +}); + +describe("DomainFormModal routing-only hostnames", () => { + it("hydrates existing routing-only hostnames into their own inputs", () => { + mountModal({ + id: "d1", + service: "web", + container_port: 80, + domain: "api.example.com", + ssl: { enabled: true, auto_cert: true }, + route_only_aliases: ["dashboard.example.com"], + }); + + const inputs = Array.from(document.querySelectorAll("input")).map((i) => i.value); + expect(inputs).toContain("dashboard.example.com"); + }); + + it("emits routing-only hostnames separately from certificate-bearing aliases", async () => { + const wrapper = mountModal({ + id: "d1", + service: "web", + container_port: 80, + domain: "api.example.com", + ssl: { enabled: true, auto_cert: true }, + aliases: ["www.example.com"], + route_only_aliases: ["dashboard.example.com"], + }); + + document.querySelector(".btn-primary")?.click(); + await wrapper.vm.$nextTick(); + + const saved = wrapper.emitted("save")?.[0]?.[0] as DomainConfig; + expect(saved.aliases).toEqual(["www.example.com"]); + expect(saved.route_only_aliases).toEqual(["dashboard.example.com"]); + }); +}); diff --git a/src/components/DomainFormModal.vue b/src/components/DomainFormModal.vue index bcce9f5..db5c897 100644 --- a/src/components/DomainFormModal.vue +++ b/src/components/DomainFormModal.vue @@ -89,6 +89,31 @@ Additional domain names that should also resolve here + +
+ +
+
+ + +
+ +
+ + Routed here but never issued a certificate, for a hostname whose TLS is terminated by an external proxy + (e.g. Cloudflare). It shares this domain's certificate over the proxy's SNI. + +