mirror of
https://github.com/fosrl/pangolin.git
synced 2026-10-29 15:09:07 +01:00
Custom dns records
This commit is contained in:
@@ -0,0 +1,370 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
Credenza,
|
||||
CredenzaBody,
|
||||
CredenzaClose,
|
||||
CredenzaContent,
|
||||
CredenzaDescription,
|
||||
CredenzaFooter,
|
||||
CredenzaHeader,
|
||||
CredenzaTitle
|
||||
} from "@app/components/Credenza";
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormDescription,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage
|
||||
} from "@app/components/ui/form";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { Input } from "@app/components/ui/input";
|
||||
import { Textarea } from "@app/components/ui/textarea";
|
||||
import { Switch } from "@app/components/ui/switch";
|
||||
import {
|
||||
Select,
|
||||
SelectContent,
|
||||
SelectItem,
|
||||
SelectTrigger,
|
||||
SelectValue
|
||||
} from "@app/components/ui/select";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import {
|
||||
CUSTOM_DNS_RECORD_TYPES,
|
||||
CustomDnsRecordRow,
|
||||
CustomDnsRecordType
|
||||
} from "@server/routers/customDnsRecord/types";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useForm } from "react-hook-form";
|
||||
import { z } from "zod";
|
||||
import { zodResolver } from "@hookform/resolvers/zod";
|
||||
|
||||
const FORM_ID = "custom-dns-record-form";
|
||||
|
||||
const VALUE_PLACEHOLDERS: Record<CustomDnsRecordType, string> = {
|
||||
A: "192.0.2.1",
|
||||
AAAA: "2001:db8::1",
|
||||
CNAME: "target.example.net",
|
||||
MX: "mail.example.net",
|
||||
NS: "ns1.example.net",
|
||||
SRV: "sip.example.net",
|
||||
TXT: "v=spf1 include:example.net ~all"
|
||||
};
|
||||
|
||||
const optionalUint16 = z.number().int().min(0).max(65535).optional();
|
||||
|
||||
const formSchema = z.object({
|
||||
type: z.enum(CUSTOM_DNS_RECORD_TYPES),
|
||||
name: z.string().max(253),
|
||||
value: z.string().min(1),
|
||||
ttl: z.number().int().min(60).max(86400),
|
||||
priority: optionalUint16,
|
||||
weight: optionalUint16,
|
||||
port: optionalUint16,
|
||||
enabled: z.boolean()
|
||||
});
|
||||
|
||||
type FormValues = z.infer<typeof formSchema>;
|
||||
|
||||
type CustomDnsRecordCredenzaProps = {
|
||||
open: boolean;
|
||||
setOpen: (open: boolean) => void;
|
||||
orgId: string;
|
||||
domainId: string;
|
||||
baseDomain: string;
|
||||
// null to create a new record
|
||||
record: CustomDnsRecordRow | null;
|
||||
onSaved: () => void;
|
||||
};
|
||||
|
||||
export default function CustomDnsRecordCredenza({
|
||||
open,
|
||||
setOpen,
|
||||
orgId,
|
||||
domainId,
|
||||
baseDomain,
|
||||
record,
|
||||
onSaved
|
||||
}: CustomDnsRecordCredenzaProps) {
|
||||
const t = useTranslations();
|
||||
const api = createApiClient(useEnvContext());
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
const form = useForm<FormValues>({
|
||||
resolver: zodResolver(formSchema),
|
||||
defaultValues: {
|
||||
type: "A",
|
||||
name: "",
|
||||
value: "",
|
||||
ttl: 300,
|
||||
enabled: true
|
||||
}
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) {
|
||||
return;
|
||||
}
|
||||
form.reset({
|
||||
type: record?.type ?? "A",
|
||||
name: record?.name ?? "",
|
||||
value: record?.value ?? "",
|
||||
ttl: record?.ttl ?? 300,
|
||||
priority: record?.priority ?? undefined,
|
||||
weight: record?.weight ?? undefined,
|
||||
port: record?.port ?? undefined,
|
||||
enabled: record?.enabled ?? true
|
||||
});
|
||||
}, [open, record, form]);
|
||||
|
||||
const type = form.watch("type");
|
||||
const needsPriority = type === "MX" || type === "SRV";
|
||||
const isSrv = type === "SRV";
|
||||
|
||||
async function onSubmit(data: FormValues) {
|
||||
setLoading(true);
|
||||
const payload = {
|
||||
type: data.type,
|
||||
name: data.name,
|
||||
value: data.value,
|
||||
ttl: data.ttl,
|
||||
priority: needsPriority ? (data.priority ?? null) : null,
|
||||
weight: isSrv ? (data.weight ?? null) : null,
|
||||
port: isSrv ? (data.port ?? null) : null,
|
||||
enabled: data.enabled
|
||||
};
|
||||
|
||||
try {
|
||||
const res = record
|
||||
? await api.post(
|
||||
`/org/${orgId}/domain/${domainId}/custom-dns-record/${record.customDnsRecordId}`,
|
||||
payload
|
||||
)
|
||||
: await api.put(
|
||||
`/org/${orgId}/domain/${domainId}/custom-dns-record`,
|
||||
payload
|
||||
);
|
||||
|
||||
if (res.status === 200 || res.status === 201) {
|
||||
toast({
|
||||
title: record
|
||||
? t("customDnsRecordUpdated")
|
||||
: t("customDnsRecordCreated")
|
||||
});
|
||||
setOpen(false);
|
||||
onSaved();
|
||||
}
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("customDnsRecordSaveError"),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
const numberField = (
|
||||
name: "ttl" | "priority" | "weight" | "port",
|
||||
label: string
|
||||
) => (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name={name}
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex-1">
|
||||
<FormLabel>{label}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
type="number"
|
||||
autoComplete="off"
|
||||
name={field.name}
|
||||
ref={field.ref}
|
||||
onBlur={field.onBlur}
|
||||
value={field.value ?? ""}
|
||||
onChange={(e) => {
|
||||
const v = e.target.value;
|
||||
field.onChange(
|
||||
v === "" ? undefined : Number(v)
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<Credenza open={open} onOpenChange={setOpen}>
|
||||
<CredenzaContent>
|
||||
<CredenzaHeader>
|
||||
<CredenzaTitle>
|
||||
{record
|
||||
? t("customDnsRecordEdit")
|
||||
: t("customDnsRecordCreate")}
|
||||
</CredenzaTitle>
|
||||
<CredenzaDescription>
|
||||
{t("customDnsRecordFormDescription", {
|
||||
domain: baseDomain
|
||||
})}
|
||||
</CredenzaDescription>
|
||||
</CredenzaHeader>
|
||||
<CredenzaBody>
|
||||
<Form {...form}>
|
||||
<form
|
||||
id={FORM_ID}
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-4"
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("type")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={field.value}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{CUSTOM_DNS_RECORD_TYPES.map(
|
||||
(recordType) => (
|
||||
<SelectItem
|
||||
key={recordType}
|
||||
value={recordType}
|
||||
>
|
||||
{recordType}
|
||||
</SelectItem>
|
||||
)
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("name")}</FormLabel>
|
||||
<div className="flex items-center gap-2">
|
||||
<FormControl>
|
||||
<Input
|
||||
autoComplete="off"
|
||||
placeholder="@"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<span className="text-sm text-muted-foreground whitespace-nowrap">
|
||||
.{baseDomain}
|
||||
</span>
|
||||
</div>
|
||||
<FormDescription>
|
||||
{t(
|
||||
"customDnsRecordNameDescription"
|
||||
)}
|
||||
</FormDescription>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="value"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("value")}</FormLabel>
|
||||
<FormControl>
|
||||
{type === "TXT" ? (
|
||||
<Textarea
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
VALUE_PLACEHOLDERS[type]
|
||||
}
|
||||
{...field}
|
||||
/>
|
||||
) : (
|
||||
<Input
|
||||
autoComplete="off"
|
||||
placeholder={
|
||||
VALUE_PLACEHOLDERS[type]
|
||||
}
|
||||
{...field}
|
||||
/>
|
||||
)}
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{needsPriority && (
|
||||
<div className="flex gap-4">
|
||||
{numberField(
|
||||
"priority",
|
||||
t("customDnsRecordPriority")
|
||||
)}
|
||||
{isSrv &&
|
||||
numberField(
|
||||
"weight",
|
||||
t("customDnsRecordWeight")
|
||||
)}
|
||||
{isSrv &&
|
||||
numberField(
|
||||
"port",
|
||||
t("customDnsRecordPort")
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{numberField("ttl", t("TTL"))}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="enabled"
|
||||
render={({ field }) => (
|
||||
<FormItem className="flex flex-row items-center gap-3 space-y-0">
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="!mt-0">
|
||||
{t("enabled")}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
<CredenzaClose asChild>
|
||||
<Button variant="outline">{t("close")}</Button>
|
||||
</CredenzaClose>
|
||||
<Button
|
||||
type="submit"
|
||||
form={FORM_ID}
|
||||
loading={loading}
|
||||
disabled={loading}
|
||||
>
|
||||
{t("save")}
|
||||
</Button>
|
||||
</CredenzaFooter>
|
||||
</CredenzaContent>
|
||||
</Credenza>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,257 @@
|
||||
"use client";
|
||||
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { useState } from "react";
|
||||
import { useTranslations } from "next-intl";
|
||||
import { Pencil, Plus, Trash2 } from "lucide-react";
|
||||
import {
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableHead,
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from "@app/components/ui/table";
|
||||
import {
|
||||
Card,
|
||||
CardContent,
|
||||
CardDescription,
|
||||
CardHeader,
|
||||
CardTitle
|
||||
} from "@app/components/ui/card";
|
||||
import { Button } from "@app/components/ui/button";
|
||||
import { Switch } from "@app/components/ui/switch";
|
||||
import CopyToClipboard from "@app/components/CopyToClipboard";
|
||||
import ConfirmDeleteDialog from "@app/components/ConfirmDeleteDialog";
|
||||
import CustomDnsRecordCredenza from "@app/components/CustomDnsRecordCredenza";
|
||||
import { PaidFeaturesAlert } from "@app/components/PaidFeaturesAlert";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { domainQueries } from "@app/lib/queries";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { toast } from "@app/hooks/useToast";
|
||||
import { createApiClient, formatAxiosError } from "@app/lib/api";
|
||||
import { CustomDnsRecordRow } from "@server/routers/customDnsRecord/types";
|
||||
|
||||
type Props = {
|
||||
orgId: string;
|
||||
domainId: string;
|
||||
baseDomain: string;
|
||||
};
|
||||
|
||||
function formatValue(record: CustomDnsRecordRow): string {
|
||||
if (record.type === "MX") {
|
||||
return `${record.priority} ${record.value}`;
|
||||
}
|
||||
if (record.type === "SRV") {
|
||||
return `${record.priority} ${record.weight} ${record.port} ${record.value}`;
|
||||
}
|
||||
return record.value;
|
||||
}
|
||||
|
||||
export default function CustomDnsRecordsTable({
|
||||
orgId,
|
||||
domainId,
|
||||
baseDomain
|
||||
}: Props) {
|
||||
const t = useTranslations();
|
||||
const api = createApiClient(useEnvContext());
|
||||
const { isPaidUser } = usePaidStatus();
|
||||
const isDisabled = !isPaidUser(tierMatrix.customDnsRecords);
|
||||
|
||||
const { data: records = [], refetch } = useQuery(
|
||||
domainQueries.listCustomDnsRecords({ orgId, domainId })
|
||||
);
|
||||
|
||||
const [editorOpen, setEditorOpen] = useState(false);
|
||||
const [editingRecord, setEditingRecord] =
|
||||
useState<CustomDnsRecordRow | null>(null);
|
||||
const [deleteOpen, setDeleteOpen] = useState(false);
|
||||
const [deletingRecord, setDeletingRecord] =
|
||||
useState<CustomDnsRecordRow | null>(null);
|
||||
|
||||
function openEditor(record: CustomDnsRecordRow | null) {
|
||||
setEditingRecord(record);
|
||||
setEditorOpen(true);
|
||||
}
|
||||
|
||||
async function toggleEnabled(record: CustomDnsRecordRow, enabled: boolean) {
|
||||
try {
|
||||
await api.post(
|
||||
`/org/${orgId}/domain/${domainId}/custom-dns-record/${record.customDnsRecordId}`,
|
||||
{ enabled }
|
||||
);
|
||||
refetch();
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("customDnsRecordSaveError"),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteRecord() {
|
||||
if (!deletingRecord) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.delete(
|
||||
`/org/${orgId}/domain/${domainId}/custom-dns-record/${deletingRecord.customDnsRecordId}`
|
||||
);
|
||||
toast({ title: t("customDnsRecordDeleted") });
|
||||
setDeleteOpen(false);
|
||||
setDeletingRecord(null);
|
||||
refetch();
|
||||
} catch (e) {
|
||||
toast({
|
||||
variant: "destructive",
|
||||
title: t("customDnsRecordDeleteError"),
|
||||
description: formatAxiosError(e)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<PaidFeaturesAlert tiers={tierMatrix.customDnsRecords} />
|
||||
<Card
|
||||
className={
|
||||
isDisabled ? "pointer-events-none opacity-50" : undefined
|
||||
}
|
||||
>
|
||||
<CardHeader className="flex flex-col space-y-4 sm:flex-row sm:items-center sm:justify-between sm:space-y-0 pb-4">
|
||||
<div className="space-y-1">
|
||||
<CardTitle>{t("customDnsRecords")}</CardTitle>
|
||||
<CardDescription>
|
||||
{t("customDnsRecordsDescription")}
|
||||
</CardDescription>
|
||||
</div>
|
||||
<Button
|
||||
onClick={() => openEditor(null)}
|
||||
disabled={isDisabled}
|
||||
>
|
||||
<Plus className="h-4 w-4 mr-1" />
|
||||
{t("customDnsRecordsAdd")}
|
||||
</Button>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Table>
|
||||
<TableHeader>
|
||||
<TableRow>
|
||||
<TableHead>{t("name")}</TableHead>
|
||||
<TableHead>{t("type")}</TableHead>
|
||||
<TableHead>{t("value")}</TableHead>
|
||||
<TableHead>{t("TTL")}</TableHead>
|
||||
<TableHead>{t("enabled")}</TableHead>
|
||||
<TableHead className="text-right">
|
||||
{t("actions")}
|
||||
</TableHead>
|
||||
</TableRow>
|
||||
</TableHeader>
|
||||
<TableBody>
|
||||
{records.length ? (
|
||||
records.map((record) => (
|
||||
<TableRow key={record.customDnsRecordId}>
|
||||
<TableCell>
|
||||
<CopyToClipboard
|
||||
text={record.fullDomain}
|
||||
displayText={record.fullDomain}
|
||||
isLink={false}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>{record.type}</TableCell>
|
||||
<TableCell className="max-w-md break-all">
|
||||
{formatValue(record)}
|
||||
</TableCell>
|
||||
<TableCell>{record.ttl}</TableCell>
|
||||
<TableCell>
|
||||
<Switch
|
||||
checked={record.enabled}
|
||||
disabled={isDisabled}
|
||||
onCheckedChange={(checked) =>
|
||||
toggleEnabled(
|
||||
record,
|
||||
checked
|
||||
)
|
||||
}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell className="text-right">
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={t("edit")}
|
||||
disabled={isDisabled}
|
||||
onClick={() =>
|
||||
openEditor(record)
|
||||
}
|
||||
>
|
||||
<Pencil className="h-4 w-4" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="icon"
|
||||
aria-label={t("delete")}
|
||||
disabled={isDisabled}
|
||||
onClick={() => {
|
||||
setDeletingRecord(record);
|
||||
setDeleteOpen(true);
|
||||
}}
|
||||
>
|
||||
<Trash2 className="h-4 w-4" />
|
||||
</Button>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
))
|
||||
) : (
|
||||
<TableRow>
|
||||
<TableCell
|
||||
colSpan={6}
|
||||
className="h-24 text-center"
|
||||
>
|
||||
{t("customDnsRecordsEmpty")}
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
)}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<CustomDnsRecordCredenza
|
||||
open={editorOpen && !isDisabled}
|
||||
setOpen={setEditorOpen}
|
||||
orgId={orgId}
|
||||
domainId={domainId}
|
||||
baseDomain={baseDomain}
|
||||
record={editingRecord}
|
||||
onSaved={() => refetch()}
|
||||
/>
|
||||
|
||||
{deletingRecord && !isDisabled && (
|
||||
<ConfirmDeleteDialog
|
||||
open={deleteOpen}
|
||||
setOpen={(open) => {
|
||||
setDeleteOpen(open);
|
||||
if (!open) {
|
||||
setDeletingRecord(null);
|
||||
}
|
||||
}}
|
||||
string={deletingRecord.fullDomain}
|
||||
title={t("customDnsRecordDeleteTitle")}
|
||||
dialog={
|
||||
<p>
|
||||
{t("customDnsRecordDeleteQuestion", {
|
||||
type: deletingRecord.type,
|
||||
name: deletingRecord.fullDomain
|
||||
})}
|
||||
</p>
|
||||
}
|
||||
buttonText={t("customDnsRecordDeleteConfirm")}
|
||||
onConfirm={deleteRecord}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import RestartDomainButton from "@app/components/RestartDomainButton";
|
||||
import RefreshButton from "@app/components/RefreshButton";
|
||||
import SettingsSectionTitle from "@app/components/SettingsSectionTitle";
|
||||
import DomainCertForm from "@app/components/DomainCertForm";
|
||||
import CustomDnsRecordsTable from "@app/components/CustomDnsRecordsTable";
|
||||
import { build } from "@server/build";
|
||||
import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { useTranslations } from "next-intl";
|
||||
@@ -97,6 +98,17 @@ export default function DomainPageClient({
|
||||
type={domain.type}
|
||||
/>
|
||||
|
||||
{build !== "oss" &&
|
||||
env.flags.usePangolinDns &&
|
||||
!env.flags.disableEnterpriseFeatures &&
|
||||
domain.type === "ns" && (
|
||||
<CustomDnsRecordsTable
|
||||
orgId={orgId}
|
||||
domainId={domain.domainId}
|
||||
baseDomain={domain.baseDomain}
|
||||
/>
|
||||
)}
|
||||
|
||||
{domain.type === "wildcard" && !domain.configManaged && (
|
||||
<DomainCertForm
|
||||
orgId={orgId}
|
||||
|
||||
@@ -47,7 +47,11 @@ function getActionsCategories(root: boolean) {
|
||||
[t("actionUpdateOrgDomain")]: "updateOrgDomain",
|
||||
[t("actionDeleteOrgDomain")]: "deleteOrgDomain",
|
||||
[t("actionGetDNSRecords")]: "getDNSRecords",
|
||||
[t("actionRestartOrgDomain")]: "restartOrgDomain"
|
||||
[t("actionRestartOrgDomain")]: "restartOrgDomain",
|
||||
[t("actionListCustomDnsRecords")]: "listCustomDnsRecords",
|
||||
[t("actionCreateCustomDnsRecord")]: "createCustomDnsRecord",
|
||||
[t("actionUpdateCustomDnsRecord")]: "updateCustomDnsRecord",
|
||||
[t("actionDeleteCustomDnsRecord")]: "deleteCustomDnsRecord"
|
||||
},
|
||||
|
||||
Redirect: {
|
||||
|
||||
@@ -43,6 +43,7 @@ import type {
|
||||
ListDomainsResponse
|
||||
} from "@server/routers/domain";
|
||||
import type { GetDomainResponse } from "@server/routers/domain/getDomain";
|
||||
import type { ListCustomDnsRecordsResponse } from "@server/routers/customDnsRecord/types";
|
||||
import { ListHealthChecksResponse } from "@server/routers/healthChecks/types";
|
||||
import type { ListOrgLabelsResponse } from "@server/routers/labels/types";
|
||||
import type { ListLauncherAiModelsResponse } from "@server/routers/launcher/listLauncherAiModels";
|
||||
@@ -1878,6 +1879,30 @@ export const domainQueries = {
|
||||
return res.data.data;
|
||||
},
|
||||
refetchInterval: durationToMs(10, "seconds")
|
||||
}),
|
||||
listCustomDnsRecords: ({
|
||||
orgId,
|
||||
domainId
|
||||
}: {
|
||||
orgId: string;
|
||||
domainId: string;
|
||||
}) =>
|
||||
queryOptions({
|
||||
queryKey: [
|
||||
"ORG",
|
||||
orgId,
|
||||
"DOMAIN",
|
||||
domainId,
|
||||
"CUSTOM_DNS_RECORDS"
|
||||
] as const,
|
||||
queryFn: async ({ signal, meta }) => {
|
||||
const res = await meta!.api.get<
|
||||
AxiosResponse<ListCustomDnsRecordsResponse>
|
||||
>(`/org/${orgId}/domain/${domainId}/custom-dns-records`, {
|
||||
signal
|
||||
});
|
||||
return res.data.data.records;
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user