feat: support custom config record types in domain config

This commit is contained in:
oiov
2025-06-13 17:16:28 +08:00
parent eeb45f67b2
commit 6ede43b9fb
11 changed files with 155 additions and 45 deletions
@@ -365,7 +365,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
className="h-7 text-nowrap px-1 text-xs sm:px-1.5"
size="sm"
variant={"outline"}
disabled
disabled={!isAdmin}
onClick={() => {
setCurrentEditRecord(record);
setShowForm(false);
+3
View File
@@ -58,6 +58,7 @@ export async function POST(req: NextRequest) {
cf_zone_id: data.cf_zone_id,
cf_api_key: data.cf_api_key,
cf_email: data.cf_email,
cf_record_types: data.cf_record_types,
cf_api_key_encrypted: false,
resend_api_key: data.resend_api_key,
max_short_links: data.max_short_links,
@@ -90,6 +91,7 @@ export async function PUT(req: NextRequest) {
cf_zone_id,
cf_api_key,
cf_email,
cf_record_types,
resend_api_key,
max_short_links,
max_email_forwards,
@@ -110,6 +112,7 @@ export async function PUT(req: NextRequest) {
cf_zone_id,
cf_api_key,
cf_email,
cf_record_types,
cf_api_key_encrypted: false,
resend_api_key,
max_short_links,
+29
View File
@@ -84,6 +84,7 @@ export function DomainForm({
cf_zone_id: initData?.cf_zone_id || "",
cf_api_key: initData?.cf_api_key || "",
cf_email: initData?.cf_email || "",
cf_record_types: initData?.cf_record_types || "CNAME,A,TXT",
cf_api_key_encrypted: initData?.cf_api_key_encrypted || false,
resend_api_key: initData?.resend_api_key || "",
max_short_links: initData?.max_short_links || 0,
@@ -466,6 +467,34 @@ export function DomainForm({
</div>
</div>
</FormSectionColumns>
<FormSectionColumns title="">
<div className="flex w-full items-start justify-between gap-2">
<Label className="mt-2.5 text-nowrap" htmlFor="record-types">
{t("Record Types")}:
</Label>
<div className="w-full sm:w-3/5">
<Input
id="record-types"
className="flex-1 bg-neutral-50 shadow-inner"
size={32}
{...register("cf_record_types")}
disabled={!currentRecordStatus}
/>
<div className="flex flex-col justify-between p-1">
{errors?.cf_record_types ? (
<p className="pb-0.5 text-[13px] text-red-600">
{errors.cf_record_types.message}
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
{t("Required")}. {t("Allowed record types")},{" "}
{t("use `,` to separate")}
</p>
)}
</div>
</div>
</div>
</FormSectionColumns>
</CollapsibleContent>
</Collapsible>
+47 -34
View File
@@ -3,6 +3,7 @@
import {
Dispatch,
SetStateAction,
useEffect,
useMemo,
useState,
useTransition,
@@ -68,6 +69,7 @@ export function RecordForm({
initData?.zone_name || "wr.do",
);
const [email, setEmail] = useState(initData?.user.email || user.email);
const [allowedRecordTypes, setAllowedRecordTypes] = useState<string[]>([]);
const isAdmin = action.indexOf("admin") > -1;
const t = useTranslations("List");
@@ -92,14 +94,12 @@ export function RecordForm({
});
// Fetch the record domains
const { data: recordDomains, isLoading } = useSWR<{ domain_name: string }[]>(
"/api/domain?feature=record",
fetcher,
{
revalidateOnFocus: false,
dedupingInterval: 10000,
},
);
const { data: recordDomains, isLoading } = useSWR<
{ domain_name: string; cf_record_types: string }[]
>("/api/domain?feature=record", fetcher, {
revalidateOnFocus: false,
dedupingInterval: 10000,
});
const { data: configs } = useSWR<Record<string, any>>(
"/api/configs?key=enable_subdomain_apply",
@@ -119,6 +119,16 @@ export function RecordForm({
return recordDomains[0].domain_name;
}, [recordDomains, initData?.zone_name]);
useEffect(() => {
if (recordDomains && recordDomains.length > 0) {
setAllowedRecordTypes(
recordDomains
.find((d) => d.domain_name === validDefaultDomain)!
.cf_record_types.split(","),
);
}
}, [currentZoneName, recordDomains, validDefaultDomain]);
const onSubmit = handleSubmit((data) => {
if (isAdmin && type === "edit" && initData?.active === 2) {
handleApplyRecord(data);
@@ -357,25 +367,29 @@ export function RecordForm({
</p>
</FormSectionColumns>
<FormSectionColumns title={t("Type")} required>
<Select
onValueChange={(value: RecordType) => {
setValue("type", value);
setCurrentRecordType(value);
}}
name={"type"}
defaultValue={initData?.type || "CNAME"}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
{RECORD_TYPE_ENUMS.map((type) => (
<SelectItem key={type.value} value={type.value}>
{type.label}
</SelectItem>
))}
</SelectContent>
</Select>
{isLoading ? (
<Skeleton className="h-9 w-full" />
) : (
<Select
onValueChange={(value: RecordType) => {
setValue("type", value);
setCurrentRecordType(value);
}}
name={"type"}
defaultValue={initData?.type || "CNAME"}
>
<SelectTrigger className="w-full shadow-inner">
<SelectValue placeholder="Select a type" />
</SelectTrigger>
<SelectContent>
{allowedRecordTypes.map((type) => (
<SelectItem key={type} value={type}>
{type}
</SelectItem>
))}
</SelectContent>
</Select>
)}
<p className="p-1 text-[13px] text-muted-foreground">
{t("Required")}.
</p>
@@ -394,8 +408,7 @@ export function RecordForm({
size={32}
{...register("name")}
/>
{(currentRecordType === "CNAME" ||
currentRecordType === "A") && (
{["CNAME", "A", "AAAA"].includes(currentRecordType) && (
<span className="absolute right-2 top-1/2 -translate-y-1/2 text-sm text-slate-500">
.{currentZoneName}
</span>
@@ -417,9 +430,9 @@ export function RecordForm({
<FormSectionColumns
required
title={
currentRecordType === "CNAME"
["CNAME"].includes(currentRecordType)
? t("Content")
: currentRecordType === "A"
: ["A", "AAAA"].includes(currentRecordType)
? t("IPv4 address")
: t("Content")
}
@@ -442,9 +455,9 @@ export function RecordForm({
</p>
) : (
<p className="pb-0.5 text-[13px] text-muted-foreground">
{currentRecordType === "CNAME"
{["CNAME"].includes(currentRecordType)
? `${t("Required")}. ${t("Example")} www.example.com`
: currentRecordType === "A"
: ["A", "AAAA"].includes(currentRecordType)
? `${t("Required")}. ${t("Example")} 8.8.8.8`
: t("Required")}
</p>
@@ -476,7 +489,7 @@ export function RecordForm({
{t("Optional")}. {t("Time To Live")}.
</p>
</FormSectionColumns>
{["A", "CNAME"].includes(currentRecordType) && (
{["CNAME", "A", "AAAA"].includes(currentRecordType) && (
<FormSectionColumns title={t("Proxy")}>
<div className="flex w-full items-center gap-2">
<Label className="sr-only" htmlFor="proxy">
+3 -7
View File
@@ -1,12 +1,5 @@
import { Domain } from "@prisma/client";
import { prisma } from "../db";
// In-memory cache
let domainConfigCache: Domain[] | null = null;
let lastCacheUpdate = 0;
const CACHE_DURATION = 60 * 1000;
export const FeatureMap = {
short: "enable_short_link",
email: "enable_email",
@@ -21,6 +14,7 @@ export interface DomainConfig {
cf_zone_id: string | null;
cf_api_key: string | null;
cf_email: string | null;
cf_record_types: string;
cf_api_key_encrypted: boolean;
resend_api_key: string | null;
max_short_links: number | null;
@@ -82,6 +76,7 @@ export async function getDomainsByFeature(
cf_zone_id: admin,
cf_api_key: admin,
cf_email: admin,
cf_record_types: true,
},
});
return domains;
@@ -96,6 +91,7 @@ export async function getDomainsByFeatureClient(feature: string) {
where: { [feature]: true },
select: {
domain_name: true,
cf_record_types: true,
},
orderBy: {
updatedAt: "desc",
+1
View File
@@ -9,6 +9,7 @@ export const createDomainSchema = z.object({
cf_zone_id: z.string().optional(),
cf_api_key: z.string().optional(),
cf_email: z.string().optional(),
cf_record_types: z.string().optional(),
cf_api_key_encrypted: z.boolean().default(false),
resend_api_key: z.string().optional(),
max_short_links: z.number().optional(),
+4 -1
View File
@@ -163,7 +163,10 @@
"Monthly limit of tracked clicks (times)": "Monthly limit of tracked clicks (times)",
"Limit on the number of allowed domains": "Limit on the number of allowed domains",
"Only active plans can be used": "Only active plans can be used",
"Plan name must be unique": "Plan name must be unique"
"Plan name must be unique": "Plan name must be unique",
"Record Types": "Record Types",
"Allowed record types": "Allowed record types",
"use `,` to separate": "use `,` to separate"
},
"Components": {
"Dashboard": "Dashboard",
+4 -1
View File
@@ -163,7 +163,10 @@
"Monthly limit of tracked clicks (times)": "每月最大跟踪点击量限制(次)",
"Limit on the number of allowed domains": "允许的域名数量限制",
"Only active plans can be used": "只有启用的计划才能生效",
"Plan name must be unique": "计划名称必须唯一"
"Plan name must be unique": "计划名称必须唯一",
"Record Types": "DNS 记录类型",
"Allowed record types": "请填写标准的 DNS 记录类型",
"use `,` to separate": "使用 `,` 分隔"
},
"Components": {
"Dashboard": "用户面板",
@@ -0,0 +1,61 @@
ALTER TABLE "domains" ADD COLUMN "cf_record_types" TEXT NOT NULL DEFAULT 'CNAME,A,TXT';
INSERT INTO "system_configs"
(
"key",
"value",
"type",
"description"
)
VALUES
(
'enable_github_oauth',
'true',
'BOOLEAN',
'是否启用 GitHub OAuth 登录'
);
INSERT INTO "system_configs"
(
"key",
"value",
"type",
"description"
)
VALUES
(
'enable_google_oauth',
'true',
'BOOLEAN',
'是否启用 Google OAuth 登录'
);
INSERT INTO "system_configs"
(
"key",
"value",
"type",
"description"
)
VALUES
(
'enable_liunxdo_oauth',
'true',
'BOOLEAN',
'是否启用 LiunxDo OAuth 登录'
);
INSERT INTO "system_configs"
(
"key",
"value",
"type",
"description"
)
VALUES
(
'enable_resend_email_login',
'true',
'BOOLEAN',
'是否启用 Resend 邮箱登录'
);
+1
View File
@@ -261,6 +261,7 @@ model Domain {
cf_zone_id String?
cf_api_key String?
cf_email String?
cf_record_types String @default("CNAME,A,TXT")
cf_api_key_encrypted Boolean
resend_api_key String?
max_short_links Int?
+1 -1
View File
File diff suppressed because one or more lines are too long