feat: support custom config record types in domain config
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
@@ -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
@@ -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",
|
||||
|
||||
@@ -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
@@ -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
@@ -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 邮箱登录'
|
||||
);
|
||||
@@ -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
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user