From 60e52cf11a3a5b1aa2ad5b9f110c7b2802e08f22 Mon Sep 17 00:00:00 2001 From: oiov Date: Wed, 6 Nov 2024 17:34:13 +0800 Subject: [PATCH] add statu switcher --- .../dashboard/records/record-list.tsx | 92 ++++++++++++++++++- app/api/record/add/route.ts | 2 +- app/api/record/update/route.ts | 61 +++++++++++- components/forms/record-form.tsx | 2 +- components/shared/icons.tsx | 3 + lib/dto/cloudflare-dns-record.ts | 4 +- 6 files changed, 151 insertions(+), 13 deletions(-) diff --git a/app/(protected)/dashboard/records/record-list.tsx b/app/(protected)/dashboard/records/record-list.tsx index da54af2..e22160b 100644 --- a/app/(protected)/dashboard/records/record-list.tsx +++ b/app/(protected)/dashboard/records/record-list.tsx @@ -1,9 +1,10 @@ "use client"; -import { useState } from "react"; +import { useRef, useState } from "react"; import Link from "next/link"; import { User } from "@prisma/client"; import { PenLine, RefreshCwIcon } from "lucide-react"; +import { toast } from "sonner"; import useSWR, { useSWRConfig } from "swr"; import { UserRecordFormData } from "@/lib/dto/cloudflare-dns-record"; @@ -19,6 +20,7 @@ import { CardTitle, } from "@/components/ui/card"; import { Skeleton } from "@/components/ui/skeleton"; +import { Switch } from "@/components/ui/switch"; import { Table, TableBody, @@ -37,6 +39,7 @@ import CountUpFn from "@/components/dashboard/count-up"; import StatusDot from "@/components/dashboard/status-dot"; import { FormType, RecordForm } from "@/components/forms/record-form"; import { EmptyPlaceholder } from "@/components/shared/empty-placeholder"; +import { Icons } from "@/components/shared/icons"; import { LinkPreviewer } from "@/components/shared/link-previewer"; import { PaginationWrapper } from "@/components/shared/pagination"; @@ -94,6 +97,38 @@ export default function UserRecordsList({ user, action }: RecordListProps) { mutate(`${action}?page=${currentPage}&size=${pageSize}`, undefined); }; + const handleChangeStatu = async ( + checked: boolean, + record: UserRecordFormData, + setChecked: (value: boolean) => void, + ) => { + const originalState = record.active === 1; + setChecked(checked); // 立即更新 UI + + const res = await fetch(`/api/record/update`, { + method: "PUT", + body: JSON.stringify({ + zone_id: record.zone_id, + record_id: record.record_id, + active: checked ? 1 : 0, + target: record.name, + }), + }); + + if (res.ok) { + const data = await res.json(); + if (data === "Target is accessible!") { + toast.success(data); + } else { + setChecked(originalState); + toast.warning(data); + } + } else { + setChecked(originalState); + toast.error("Failed to update status"); + } + }; + return ( <> @@ -186,7 +221,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) { Status - Update + Updated Actions @@ -238,8 +273,36 @@ export default function UserRecordsList({ user, action }: RecordListProps) { ?.label } - - + + + {!record.active && ( + + + + + + +
    + {/* 无序列表的dot */} +
  • The target is currently inaccessible.
  • +
  • Please check the target and try again.
  • +
  • + If the target is not activated within 3 days,{" "} +
    + the administrator will{" "} + + delete this record + + . +
  • +
+
+
+
+ )}
{timeAgo(record.modified_on as unknown as Date)} @@ -285,3 +348,24 @@ export default function UserRecordsList({ user, action }: RecordListProps) { ); } + +const SwitchWrapper = ({ + record, + onChangeStatu, +}: { + record: UserRecordFormData; + onChangeStatu: ( + checked: boolean, + record: UserRecordFormData, + setChecked: (value: boolean) => void, + ) => Promise; +}) => { + const [checked, setChecked] = useState(record.active === 1); + + return ( + onChangeStatu(value, record, setChecked)} + /> + ); +}; diff --git a/app/api/record/add/route.ts b/app/api/record/add/route.ts index 14475db..1abd1a4 100644 --- a/app/api/record/add/route.ts +++ b/app/api/record/add/route.ts @@ -89,7 +89,7 @@ export async function POST(req: Request) { tags: data.result.tags?.join("") ?? "", created_on: data.result.created_on, modified_on: data.result.modified_on, - active: 1, + active: 0, }); if (res.status !== "success") { return Response.json(res.status, { diff --git a/app/api/record/update/route.ts b/app/api/record/update/route.ts index 3d46381..897a5dc 100644 --- a/app/api/record/update/route.ts +++ b/app/api/record/update/route.ts @@ -1,9 +1,13 @@ import { env } from "@/env.mjs"; import { updateDNSRecord } from "@/lib/cloudflare"; -import { updateUserRecord } from "@/lib/dto/cloudflare-dns-record"; +import { + updateUserRecord, + updateUserRecordState, +} from "@/lib/dto/cloudflare-dns-record"; import { checkUserStatus } from "@/lib/dto/user"; import { getCurrentUser } from "@/lib/session"; +// update record export async function POST(req: Request) { try { const user = checkUserStatus(await getCurrentUser()); @@ -11,10 +15,10 @@ export async function POST(req: Request) { const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env; if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) { - return Response.json("API key、zone iD and email are required", { - status: 400, - statusText: "API key、zone iD and email are required", - }); + return Response.json( + { statusText: "API key andzone id are required." }, + { status: 401 }, + ); } const { record, recordId } = await req.json(); @@ -63,3 +67,50 @@ export async function POST(req: Request) { }); } } + +// update record state +export async function PUT(req: Request) { + try { + const user = checkUserStatus(await getCurrentUser()); + if (user instanceof Response) return user; + + const { CLOUDFLARE_ZONE_ID, CLOUDFLARE_API_KEY, CLOUDFLARE_EMAIL } = env; + if (!CLOUDFLARE_ZONE_ID || !CLOUDFLARE_API_KEY || !CLOUDFLARE_EMAIL) { + return Response.json( + { statusText: "API key and zone id are required." }, + { status: 401 }, + ); + } + + const { zone_id, record_id, target, active } = await req.json(); + + let isTargetAccessible = false; + try { + const target_res = await fetch(`https://${target}`); + isTargetAccessible = target_res.status === 200; + } catch (fetchError) { + isTargetAccessible = false; + // console.log(`Failed to access target: ${fetchError}`); + } + + const res = await updateUserRecordState( + user.id, + record_id, + zone_id, + isTargetAccessible ? 1 : 0, + ); + + if (!res) { + return Response.json( + { statusText: "An error occurred." }, + { status: 502 }, + ); + } + return Response.json( + isTargetAccessible ? "Target is accessible!" : "Target is unaccessible!", + ); + } catch (error) { + console.error(error); + return Response.json({ statusText: "Server error" }, { status: 500 }); + } +} diff --git a/components/forms/record-form.tsx b/components/forms/record-form.tsx index 5f41b57..69eca00 100644 --- a/components/forms/record-form.tsx +++ b/components/forms/record-form.tsx @@ -95,7 +95,7 @@ export function RecordForm({ description: response.statusText, }); } else { - const res = await response.json(); + // const res = await response.json(); toast.success(`Created successfully!`); setShowForm(false); onRefresh(); diff --git a/components/shared/icons.tsx b/components/shared/icons.tsx index 04671e4..2fd7c10 100644 --- a/components/shared/icons.tsx +++ b/components/shared/icons.tsx @@ -2,11 +2,13 @@ import { AlertTriangle, ArrowRight, ArrowUpRight, + BadgeHelp, BookOpen, Bug, Check, ChevronLeft, ChevronRight, + CircleHelp, Copy, File, FileText, @@ -129,6 +131,7 @@ export const Icons = { link: Link, mail: Mail, bug: Bug, + // help: CircleHelp, outLink: ({ ...props }: LucideProps) => (