feat(dns): Implement DNS preview records generation and enhance hostname preview functionality
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 56s
quality / api (push) Successful in 43s
CD / quality (push) Successful in 1m46s
CD / publish (push) Successful in 1m40s
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 5s
quality / changes (push) Successful in 5s
quality / docker-check (push) Skipped
quality / web (push) Successful in 56s
quality / api (push) Successful in 43s
CD / quality (push) Successful in 1m46s
CD / publish (push) Successful in 1m40s
- Added `buildDnsPreviewRecords` function to generate DNS records based on hostname, TTL, and provided IPv4/IPv6 addresses. - Updated `fleetRoutes` to utilize the new DNS records generation for hostname previews. - Enhanced frontend API calls to support IPv4, IPv6, and node ID parameters for more comprehensive hostname previews. - Updated UI components to display DNS preview information, including A, AAAA, and CNAME records.
This commit is contained in:
@@ -35,6 +35,7 @@ import {
|
||||
} from "@cdnmanager/db";
|
||||
import { AppError } from "../errors.js";
|
||||
import {
|
||||
buildDnsPreviewRecords,
|
||||
buildHostname,
|
||||
isValidIpv4,
|
||||
isValidIpv6,
|
||||
@@ -322,15 +323,30 @@ export const fleetRoutes: FastifyPluginAsync = async (app) => {
|
||||
const loc = listLocations(app.db).find((l) => l.id === q.locationId);
|
||||
if (!loc) throw AppError.notFound("location not found");
|
||||
const indexNum = Number(q.indexNum ?? "1") || 1;
|
||||
const hostname = buildHostname({
|
||||
template: q.template || zone.namingTemplate,
|
||||
locationCode: loc.code,
|
||||
role: q.role,
|
||||
indexNum,
|
||||
zoneName: zone.name,
|
||||
providerTag: q.providerTag,
|
||||
});
|
||||
const aliases = q.nodeId
|
||||
? listAliases(app.db, { zoneId: zone.id }).filter(
|
||||
(a) => a.targetNodeId === q.nodeId,
|
||||
)
|
||||
: [];
|
||||
const records = buildDnsPreviewRecords({
|
||||
hostname,
|
||||
ttl: zone.defaultTtl,
|
||||
ipv4: q.ipv4,
|
||||
ipv6: q.ipv6,
|
||||
aliases: aliases.map((a) => ({ name: a.name, purpose: a.purpose })),
|
||||
});
|
||||
return {
|
||||
hostname: buildHostname({
|
||||
template: q.template || zone.namingTemplate,
|
||||
locationCode: loc.code,
|
||||
role: q.role,
|
||||
indexNum,
|
||||
zoneName: zone.name,
|
||||
providerTag: q.providerTag,
|
||||
}),
|
||||
hostname,
|
||||
ttl: zone.defaultTtl,
|
||||
records,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
@@ -24,6 +24,56 @@ export function buildHostname(opts: {
|
||||
return host.replace(/\.$/, "");
|
||||
}
|
||||
|
||||
export type DnsPreviewRecord = {
|
||||
type: "A" | "AAAA" | "CNAME";
|
||||
name: string;
|
||||
content: string;
|
||||
ttl: number;
|
||||
/** purpose / note (e.g. alias purpose) */
|
||||
note?: string;
|
||||
};
|
||||
|
||||
/** Desired-state DNS for node form preview (A/AAAA + CNAME aliases → hostname). */
|
||||
export function buildDnsPreviewRecords(opts: {
|
||||
hostname: string;
|
||||
ttl: number;
|
||||
ipv4?: string | null;
|
||||
ipv6?: string | null;
|
||||
aliases?: Array<{ name: string; purpose?: string | null }>;
|
||||
}): DnsPreviewRecord[] {
|
||||
const records: DnsPreviewRecord[] = [];
|
||||
const ipv4 = opts.ipv4?.trim();
|
||||
const ipv6 = opts.ipv6?.trim();
|
||||
if (ipv4) {
|
||||
records.push({
|
||||
type: "A",
|
||||
name: opts.hostname,
|
||||
content: ipv4,
|
||||
ttl: opts.ttl,
|
||||
note: "dns-only",
|
||||
});
|
||||
}
|
||||
if (ipv6) {
|
||||
records.push({
|
||||
type: "AAAA",
|
||||
name: opts.hostname,
|
||||
content: ipv6,
|
||||
ttl: opts.ttl,
|
||||
note: "dns-only",
|
||||
});
|
||||
}
|
||||
for (const alias of opts.aliases ?? []) {
|
||||
records.push({
|
||||
type: "CNAME",
|
||||
name: alias.name,
|
||||
content: opts.hostname,
|
||||
ttl: opts.ttl,
|
||||
note: alias.purpose ?? undefined,
|
||||
});
|
||||
}
|
||||
return records;
|
||||
}
|
||||
|
||||
export function normalizeFqdn(name: string): string {
|
||||
return name.trim().toLowerCase().replace(/\.$/, "");
|
||||
}
|
||||
|
||||
@@ -112,5 +112,44 @@ describe("fleet api", () => {
|
||||
});
|
||||
expect(topo.statusCode).toBe(200);
|
||||
expect(topo.json().nodes).toHaveLength(1);
|
||||
|
||||
const preview = await app.inject({
|
||||
method: "GET",
|
||||
url: `/api/v1/naming/preview?${new URLSearchParams({
|
||||
zoneId: zone.id,
|
||||
locationId: msk.id,
|
||||
role: "gw",
|
||||
indexNum: "2",
|
||||
ipv4: "198.51.100.10",
|
||||
ipv6: "2001:db8::10",
|
||||
nodeId: node.id,
|
||||
})}`,
|
||||
headers: authHeader,
|
||||
});
|
||||
expect(preview.statusCode).toBe(200);
|
||||
const body = preview.json() as {
|
||||
hostname: string;
|
||||
records: Array<{ type: string; name: string; content: string }>;
|
||||
};
|
||||
expect(body.hostname).toBe("msk-gw02.rtnt.top");
|
||||
expect(body.records).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
type: "A",
|
||||
name: "msk-gw02.rtnt.top",
|
||||
content: "198.51.100.10",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "AAAA",
|
||||
name: "msk-gw02.rtnt.top",
|
||||
content: "2001:db8::10",
|
||||
}),
|
||||
expect.objectContaining({
|
||||
type: "CNAME",
|
||||
name: "msk.rtnt.top",
|
||||
content: "msk-gw02.rtnt.top",
|
||||
}),
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user