add statu switcher
This commit is contained in:
@@ -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 (
|
||||
<>
|
||||
<Card className="xl:col-span-2">
|
||||
@@ -186,7 +221,7 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
Status
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 hidden items-center justify-center font-bold sm:flex">
|
||||
Update
|
||||
Updated
|
||||
</TableHead>
|
||||
<TableHead className="col-span-1 flex items-center justify-center font-bold">
|
||||
Actions
|
||||
@@ -238,8 +273,36 @@ export default function UserRecordsList({ user, action }: RecordListProps) {
|
||||
?.label
|
||||
}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
<StatusDot status={record.active} />
|
||||
<TableCell className="col-span-1 hidden items-center justify-center gap-1 sm:flex">
|
||||
<SwitchWrapper
|
||||
record={record}
|
||||
onChangeStatu={handleChangeStatu}
|
||||
/>
|
||||
{!record.active && (
|
||||
<TooltipProvider>
|
||||
<Tooltip delayDuration={200}>
|
||||
<TooltipTrigger className="truncate">
|
||||
<Icons.help className="size-4 cursor-pointer text-yellow-500 opacity-90" />
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<ul className="list-disc px-3">
|
||||
{/* 无序列表的dot */}
|
||||
<li>The target is currently inaccessible.</li>
|
||||
<li>Please check the target and try again.</li>
|
||||
<li>
|
||||
If the target is not activated within 3 days,{" "}
|
||||
<br />
|
||||
the administrator will{" "}
|
||||
<strong className="text-red-500">
|
||||
delete this record
|
||||
</strong>
|
||||
.
|
||||
</li>
|
||||
</ul>
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
</TooltipProvider>
|
||||
)}
|
||||
</TableCell>
|
||||
<TableCell className="col-span-1 hidden justify-center sm:flex">
|
||||
{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<void>;
|
||||
}) => {
|
||||
const [checked, setChecked] = useState(record.active === 1);
|
||||
|
||||
return (
|
||||
<Switch
|
||||
checked={checked}
|
||||
onCheckedChange={(value) => onChangeStatu(value, record, setChecked)}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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, {
|
||||
|
||||
@@ -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 });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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) => (
|
||||
<svg
|
||||
width="0.95em"
|
||||
|
||||
@@ -89,7 +89,7 @@ export async function getUserRecords(
|
||||
role === "USER"
|
||||
? {
|
||||
userId,
|
||||
active,
|
||||
// active,
|
||||
}
|
||||
: {};
|
||||
const [total, list] = await prisma.$transaction([
|
||||
@@ -217,7 +217,7 @@ export async function updateUserRecordState(
|
||||
) {
|
||||
return await prisma.userRecord.update({
|
||||
where: {
|
||||
userId,
|
||||
// userId,
|
||||
record_id,
|
||||
zone_id,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user