From 65864d3506b3122acad7a5a67ad1ec7b02711a26 Mon Sep 17 00:00:00 2001 From: wood chen Date: Sat, 12 Jul 2025 06:21:27 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=20.gitignore=20=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BB=A5=E6=B7=BB=E5=8A=A0=E6=9B=B4=E5=A4=9A=E5=BF=BD?= =?UTF-8?q?=E7=95=A5=E8=A7=84=E5=88=99=EF=BC=8C=E5=88=A0=E9=99=A4=20index.?= =?UTF-8?q?html=20=E6=96=87=E4=BB=B6=EF=BC=8C=E6=9B=B4=E6=96=B0=20readme.m?= =?UTF-8?q?d=20=E6=96=87=E4=BB=B6=E4=BB=A5=E6=94=B9=E8=BF=9B=E9=A1=B9?= =?UTF-8?q?=E7=9B=AE=E6=8F=8F=E8=BF=B0=E5=92=8C=E9=83=A8=E7=BD=B2=E8=AF=B4?= =?UTF-8?q?=E6=98=8E=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .edgeone/functions/index.js | 503 +++ .edgeone/meta.json | 29 + .gitignore | 46 +- app/favicon.ico | Bin 0 -> 25931 bytes app/form.tsx | 684 ++++ app/globals.css | 122 + app/layout.tsx | 38 + app/page.tsx | 15 + components.json | 21 + components/ui/button.tsx | 59 + components/ui/card.tsx | 92 + components/ui/dialog.tsx | 143 + components/ui/footer.tsx | 57 + components/ui/input.tsx | 22 + components/ui/label.tsx | 24 + components/ui/sonner.tsx | 25 + components/ui/tabs.tsx | 66 + components/ui/textarea.tsx | 18 + components/ui/toast.tsx | 129 + components/ui/use-toast.ts | 189 + eslint.config.mjs | 16 + index.html | 201 - lib/utils.ts | 6 + next.config.ts | 7 + package-lock.json | 6909 +++++++++++++++++++++++++++++++++++ package.json | 39 + postcss.config.mjs | 5 + public/file.svg | 1 + public/globe.svg | 1 + public/next.svg | 1 + public/vercel.svg | 1 + public/window.svg | 1 + readme.md | 12 +- tsconfig.json | 27 + 34 files changed, 9299 insertions(+), 210 deletions(-) create mode 100644 .edgeone/functions/index.js create mode 100644 .edgeone/meta.json create mode 100644 app/favicon.ico create mode 100644 app/form.tsx create mode 100644 app/globals.css create mode 100644 app/layout.tsx create mode 100644 app/page.tsx create mode 100644 components.json create mode 100644 components/ui/button.tsx create mode 100644 components/ui/card.tsx create mode 100644 components/ui/dialog.tsx create mode 100644 components/ui/footer.tsx create mode 100644 components/ui/input.tsx create mode 100644 components/ui/label.tsx create mode 100644 components/ui/sonner.tsx create mode 100644 components/ui/tabs.tsx create mode 100644 components/ui/textarea.tsx create mode 100644 components/ui/toast.tsx create mode 100644 components/ui/use-toast.ts create mode 100644 eslint.config.mjs delete mode 100644 index.html create mode 100644 lib/utils.ts create mode 100644 next.config.ts create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.mjs create mode 100644 public/file.svg create mode 100644 public/globe.svg create mode 100644 public/next.svg create mode 100644 public/vercel.svg create mode 100644 public/window.svg create mode 100644 tsconfig.json diff --git a/.edgeone/functions/index.js b/.edgeone/functions/index.js new file mode 100644 index 0000000..dba1419 --- /dev/null +++ b/.edgeone/functions/index.js @@ -0,0 +1,503 @@ + + let global = globalThis; + + class MessageChannel { + constructor() { + this.port1 = new MessagePort(); + this.port2 = new MessagePort(); + } + } + class MessagePort { + constructor() { + this.onmessage = null; + } + postMessage(data) { + if (this.onmessage) { + setTimeout(() => this.onmessage({ data }), 0); + } + } + } + global.MessageChannel = MessageChannel; + + async function handleRequest(context){ + let routeParams = {}; + let pagesFunctionResponse = null; + const request = context.request; + const urlInfo = new URL(request.url); + + if (urlInfo.pathname !== '/' && urlInfo.pathname.endsWith('/')) { + urlInfo.pathname = urlInfo.pathname.slice(0, -1); + } + + let matchedFunc = false; + + if('/api/clean-eo-cache' === urlInfo.pathname) { + matchedFunc = true; + (() => { + // functions/api/clean-eo-cache.js + async function qcloudV3Post(secretId, secretKey, service, bodyArray, headersArray) { + const HTTPRequestMethod = "POST"; + const CanonicalURI = "/"; + const CanonicalQueryString = ""; + const sortHeadersArray = Object.keys(headersArray).sort().reduce((obj, key) => { + obj[key] = headersArray[key]; + return obj; + }, {}); + let SignedHeaders = ""; + let CanonicalHeaders = ""; + for (const key in sortHeadersArray) { + SignedHeaders += key.toLowerCase() + ";"; + } + SignedHeaders = SignedHeaders.slice(0, -1); + for (const key in sortHeadersArray) { + CanonicalHeaders += `${key.toLowerCase()}:${sortHeadersArray[key].toLowerCase()} +`; + } + const HashedRequestPayload = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(JSON.stringify(bodyArray)) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const CanonicalRequest = `${HTTPRequestMethod} +${CanonicalURI} +${CanonicalQueryString} +${CanonicalHeaders} +${SignedHeaders} +${HashedRequestPayload}`; + const RequestTimestamp = Math.floor(Date.now() / 1e3); + const formattedDate = new Date(RequestTimestamp * 1e3).toISOString().split("T")[0]; + const Algorithm = "TC3-HMAC-SHA256"; + const CredentialScope = `${formattedDate}/${service}/tc3_request`; + const HashedCanonicalRequest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(CanonicalRequest) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const StringToSign = `${Algorithm} +${RequestTimestamp} +${CredentialScope} +${HashedCanonicalRequest}`; + async function hmac(key, string) { + const cryptoKey = await crypto.subtle.importKey( + "raw", + typeof key === "string" ? new TextEncoder().encode(key) : key, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + const signature = await crypto.subtle.sign( + "HMAC", + cryptoKey, + new TextEncoder().encode(string) + ); + return new Uint8Array(signature); + } + const SecretDate = await hmac("TC3" + secretKey, formattedDate); + const SecretService = await hmac(SecretDate, service); + const SecretSigning = await hmac(SecretService, "tc3_request"); + const Signature = Array.from( + new Uint8Array( + await crypto.subtle.sign( + "HMAC", + await crypto.subtle.importKey( + "raw", + SecretSigning, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ), + new TextEncoder().encode(StringToSign) + ) + ) + ).map((b) => b.toString(16).padStart(2, "0")).join(""); + const Authorization = `${Algorithm} Credential=${secretId}/${CredentialScope}, SignedHeaders=${SignedHeaders}, Signature=${Signature}`; + headersArray["X-TC-Timestamp"] = RequestTimestamp.toString(); + headersArray["Authorization"] = Authorization; + return headersArray; + } + function onRequestOptions(context) { + return new Response(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type" + } + }); + } + async function onRequestPost(context) { + try { + const data = await context.request.json(); + const { secretId, secretKey, zoneId, type, targets } = data; + const service = "teo"; + const host = "teo.tencentcloudapi.com"; + const payload = { + ZoneId: zoneId, + Type: type, + Targets: targets + }; + const headersPending = { + "Host": host, + "Content-Type": "application/json", + "X-TC-Action": "CreatePurgeTask", + "X-TC-Version": "2022-09-01", + "X-TC-Region": "ap-guangzhou" + }; + const headers = await qcloudV3Post(secretId, secretKey, service, payload, headersPending); + const response = await fetch(`https://${host}`, { + method: "POST", + headers, + body: JSON.stringify(payload) + }); + const result = await response.json(); + return new Response(JSON.stringify(result), { + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + } + function onRequest(context) { + return new Response(JSON.stringify({ error: "Only POST method is allowed" }), { + status: 405, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + + pagesFunctionResponse = onRequest; + })(); + } + + if('/api/clean-eo-cache' === urlInfo.pathname && request.method === 'POST') { + matchedFunc = true; + (() => { + // functions/api/clean-eo-cache.js + async function qcloudV3Post(secretId, secretKey, service, bodyArray, headersArray) { + const HTTPRequestMethod = "POST"; + const CanonicalURI = "/"; + const CanonicalQueryString = ""; + const sortHeadersArray = Object.keys(headersArray).sort().reduce((obj, key) => { + obj[key] = headersArray[key]; + return obj; + }, {}); + let SignedHeaders = ""; + let CanonicalHeaders = ""; + for (const key in sortHeadersArray) { + SignedHeaders += key.toLowerCase() + ";"; + } + SignedHeaders = SignedHeaders.slice(0, -1); + for (const key in sortHeadersArray) { + CanonicalHeaders += `${key.toLowerCase()}:${sortHeadersArray[key].toLowerCase()} +`; + } + const HashedRequestPayload = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(JSON.stringify(bodyArray)) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const CanonicalRequest = `${HTTPRequestMethod} +${CanonicalURI} +${CanonicalQueryString} +${CanonicalHeaders} +${SignedHeaders} +${HashedRequestPayload}`; + const RequestTimestamp = Math.floor(Date.now() / 1e3); + const formattedDate = new Date(RequestTimestamp * 1e3).toISOString().split("T")[0]; + const Algorithm = "TC3-HMAC-SHA256"; + const CredentialScope = `${formattedDate}/${service}/tc3_request`; + const HashedCanonicalRequest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(CanonicalRequest) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const StringToSign = `${Algorithm} +${RequestTimestamp} +${CredentialScope} +${HashedCanonicalRequest}`; + async function hmac(key, string) { + const cryptoKey = await crypto.subtle.importKey( + "raw", + typeof key === "string" ? new TextEncoder().encode(key) : key, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + const signature = await crypto.subtle.sign( + "HMAC", + cryptoKey, + new TextEncoder().encode(string) + ); + return new Uint8Array(signature); + } + const SecretDate = await hmac("TC3" + secretKey, formattedDate); + const SecretService = await hmac(SecretDate, service); + const SecretSigning = await hmac(SecretService, "tc3_request"); + const Signature = Array.from( + new Uint8Array( + await crypto.subtle.sign( + "HMAC", + await crypto.subtle.importKey( + "raw", + SecretSigning, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ), + new TextEncoder().encode(StringToSign) + ) + ) + ).map((b) => b.toString(16).padStart(2, "0")).join(""); + const Authorization = `${Algorithm} Credential=${secretId}/${CredentialScope}, SignedHeaders=${SignedHeaders}, Signature=${Signature}`; + headersArray["X-TC-Timestamp"] = RequestTimestamp.toString(); + headersArray["Authorization"] = Authorization; + return headersArray; + } + function onRequestOptions(context) { + return new Response(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type" + } + }); + } + async function onRequestPost(context) { + try { + const data = await context.request.json(); + const { secretId, secretKey, zoneId, type, targets } = data; + const service = "teo"; + const host = "teo.tencentcloudapi.com"; + const payload = { + ZoneId: zoneId, + Type: type, + Targets: targets + }; + const headersPending = { + "Host": host, + "Content-Type": "application/json", + "X-TC-Action": "CreatePurgeTask", + "X-TC-Version": "2022-09-01", + "X-TC-Region": "ap-guangzhou" + }; + const headers = await qcloudV3Post(secretId, secretKey, service, payload, headersPending); + const response = await fetch(`https://${host}`, { + method: "POST", + headers, + body: JSON.stringify(payload) + }); + const result = await response.json(); + return new Response(JSON.stringify(result), { + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + } + function onRequest(context) { + return new Response(JSON.stringify({ error: "Only POST method is allowed" }), { + status: 405, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + + pagesFunctionResponse = onRequestPost; + })(); + } + + if('/api/clean-eo-cache' === urlInfo.pathname && request.method === 'OPTIONS') { + matchedFunc = true; + (() => { + // functions/api/clean-eo-cache.js + async function qcloudV3Post(secretId, secretKey, service, bodyArray, headersArray) { + const HTTPRequestMethod = "POST"; + const CanonicalURI = "/"; + const CanonicalQueryString = ""; + const sortHeadersArray = Object.keys(headersArray).sort().reduce((obj, key) => { + obj[key] = headersArray[key]; + return obj; + }, {}); + let SignedHeaders = ""; + let CanonicalHeaders = ""; + for (const key in sortHeadersArray) { + SignedHeaders += key.toLowerCase() + ";"; + } + SignedHeaders = SignedHeaders.slice(0, -1); + for (const key in sortHeadersArray) { + CanonicalHeaders += `${key.toLowerCase()}:${sortHeadersArray[key].toLowerCase()} +`; + } + const HashedRequestPayload = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(JSON.stringify(bodyArray)) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const CanonicalRequest = `${HTTPRequestMethod} +${CanonicalURI} +${CanonicalQueryString} +${CanonicalHeaders} +${SignedHeaders} +${HashedRequestPayload}`; + const RequestTimestamp = Math.floor(Date.now() / 1e3); + const formattedDate = new Date(RequestTimestamp * 1e3).toISOString().split("T")[0]; + const Algorithm = "TC3-HMAC-SHA256"; + const CredentialScope = `${formattedDate}/${service}/tc3_request`; + const HashedCanonicalRequest = await crypto.subtle.digest( + "SHA-256", + new TextEncoder().encode(CanonicalRequest) + ).then((hash) => Array.from(new Uint8Array(hash)).map((b) => b.toString(16).padStart(2, "0")).join("")); + const StringToSign = `${Algorithm} +${RequestTimestamp} +${CredentialScope} +${HashedCanonicalRequest}`; + async function hmac(key, string) { + const cryptoKey = await crypto.subtle.importKey( + "raw", + typeof key === "string" ? new TextEncoder().encode(key) : key, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ); + const signature = await crypto.subtle.sign( + "HMAC", + cryptoKey, + new TextEncoder().encode(string) + ); + return new Uint8Array(signature); + } + const SecretDate = await hmac("TC3" + secretKey, formattedDate); + const SecretService = await hmac(SecretDate, service); + const SecretSigning = await hmac(SecretService, "tc3_request"); + const Signature = Array.from( + new Uint8Array( + await crypto.subtle.sign( + "HMAC", + await crypto.subtle.importKey( + "raw", + SecretSigning, + { name: "HMAC", hash: "SHA-256" }, + false, + ["sign"] + ), + new TextEncoder().encode(StringToSign) + ) + ) + ).map((b) => b.toString(16).padStart(2, "0")).join(""); + const Authorization = `${Algorithm} Credential=${secretId}/${CredentialScope}, SignedHeaders=${SignedHeaders}, Signature=${Signature}`; + headersArray["X-TC-Timestamp"] = RequestTimestamp.toString(); + headersArray["Authorization"] = Authorization; + return headersArray; + } + function onRequestOptions(context) { + return new Response(null, { + headers: { + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "POST, OPTIONS", + "Access-Control-Allow-Headers": "Content-Type" + } + }); + } + async function onRequestPost(context) { + try { + const data = await context.request.json(); + const { secretId, secretKey, zoneId, type, targets } = data; + const service = "teo"; + const host = "teo.tencentcloudapi.com"; + const payload = { + ZoneId: zoneId, + Type: type, + Targets: targets + }; + const headersPending = { + "Host": host, + "Content-Type": "application/json", + "X-TC-Action": "CreatePurgeTask", + "X-TC-Version": "2022-09-01", + "X-TC-Region": "ap-guangzhou" + }; + const headers = await qcloudV3Post(secretId, secretKey, service, payload, headersPending); + const response = await fetch(`https://${host}`, { + method: "POST", + headers, + body: JSON.stringify(payload) + }); + const result = await response.json(); + return new Response(JSON.stringify(result), { + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } catch (error) { + return new Response(JSON.stringify({ error: error.message }), { + status: 500, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + } + function onRequest(context) { + return new Response(JSON.stringify({ error: "Only POST method is allowed" }), { + status: 405, + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*" + } + }); + } + + pagesFunctionResponse = onRequestOptions; + })(); + } + + + const params = {}; + if (routeParams.id) { + if (routeParams.mode === 1) { + const value = urlInfo.pathname.match(routeParams.left); + for (let i = 1; i < value.length; i++) { + params[routeParams.id[i - 1]] = value[i]; + } + } else { + const value = urlInfo.pathname.replace(routeParams.left, ''); + const splitedValue = value.split('/'); + if (splitedValue.length === 1) { + params[routeParams.id] = splitedValue[0]; + } else { + params[routeParams.id] = splitedValue; + } + } + + } + if(!matchedFunc){ + pagesFunctionResponse = function() { + return new Response(null, { + status: 404, + headers: { + "content-type": "text/html; charset=UTF-8", + "x-edgefunctions-test": "Welcome to use Pages Functions.", + }, + }); + } + } + return pagesFunctionResponse({request, params, env: {} }); + }addEventListener('fetch',event=>{return event.respondWith(handleRequest({request:event.request,params: {}, env: {} }))}); \ No newline at end of file diff --git a/.edgeone/meta.json b/.edgeone/meta.json new file mode 100644 index 0000000..e720d15 --- /dev/null +++ b/.edgeone/meta.json @@ -0,0 +1,29 @@ +{ + "conf": {}, + "routes": [ + { + "routePath": "/api/clean-eo-cache", + "mountPath": "/api", + "method": "", + "module": [ + "api/clean-eo-cache.js:onRequest" + ] + }, + { + "routePath": "/api/clean-eo-cache", + "mountPath": "/api", + "method": "POST", + "module": [ + "api/clean-eo-cache.js:onRequestPost" + ] + }, + { + "routePath": "/api/clean-eo-cache", + "mountPath": "/api", + "method": "OPTIONS", + "module": [ + "api/clean-eo-cache.js:onRequestOptions" + ] + } + ] +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index e474f3b..5ef6a52 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,41 @@ -# Tencent Cloud EdgeOne -.env -.edgeone/* -!.edgeone/project.json -.tef_dist/* \ No newline at end of file +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/app/favicon.ico b/app/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..718d6fea4835ec2d246af9800eddb7ffb276240c GIT binary patch literal 25931 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 LrJugUA?W`A8`#=m literal 0 HcmV?d00001 diff --git a/app/form.tsx b/app/form.tsx new file mode 100644 index 0000000..1be2216 --- /dev/null +++ b/app/form.tsx @@ -0,0 +1,684 @@ +"use client"; + +import { useState, useEffect } from "react"; +import { Button } from "@/components/ui/button"; +import { Input } from "@/components/ui/input"; +import { Textarea } from "@/components/ui/textarea"; +import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { useToast } from "@/components/ui/use-toast"; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter } from "@/components/ui/dialog"; +import { Label } from "@/components/ui/label"; +import { Save, Trash, CheckCircle2, XCircle, Pencil } from "lucide-react"; +import Link from "next/link"; + +interface SavedConfig { + id: string; + name: string; + secretId: string; + secretKey: string; + zoneId: string; +} + +interface EOApiResponse { + Response: { + RequestId: string; + JobId: string; + FailedList: string[]; + Error?: { + Message: string; + Code: string; + }; + }; +} + +export function CleanCacheForm() { + const [secretId, setSecretId] = useState(""); + const [secretKey, setSecretKey] = useState(""); + const [zoneId, setZoneId] = useState(""); + const [loading, setLoading] = useState(false); + const [dialogOpen, setDialogOpen] = useState(false); + const [result, setResult] = useState(null); + const [savedConfigs, setSavedConfigs] = useState([]); + const [saveDialogOpen, setSaveDialogOpen] = useState(false); + const [configName, setConfigName] = useState(""); + const [editingConfigId, setEditingConfigId] = useState(null); + const [urls, setUrls] = useState(""); + const [prefixes, setPrefixes] = useState(""); + const [hosts, setHosts] = useState(""); + const [tags, setTags] = useState(""); + const { toast } = useToast(); + + useEffect(() => { + // 加载保存的配置列表 + const configs = localStorage.getItem("savedConfigs"); + if (configs) { + setSavedConfigs(JSON.parse(configs)); + } + }, []); + + const saveConfig = () => { + if (!configName.trim()) { + toast({ + variant: "destructive", + title: "错误", + description: "请输入配置名称", + }); + return; + } + + if (!secretId || !secretKey || !zoneId) { + toast({ + variant: "destructive", + title: "错误", + description: "请填写完整的配置信息!", + }); + return; + } + + let updatedConfigs: SavedConfig[]; + + if (editingConfigId) { + // 编辑现有配置 + updatedConfigs = savedConfigs.map(config => { + if (config.id === editingConfigId) { + return { + ...config, + name: configName, + secretId, + secretKey, + zoneId + }; + } + return config; + }); + + toast({ + title: "成功", + description: "配置已更新", + }); + } else { + // 添加新配置 + const newConfig: SavedConfig = { + id: Date.now().toString(), + name: configName, + secretId, + secretKey, + zoneId, + }; + + updatedConfigs = [...savedConfigs, newConfig]; + + toast({ + title: "成功", + description: "配置已保存", + }); + } + + setSavedConfigs(updatedConfigs); + localStorage.setItem("savedConfigs", JSON.stringify(updatedConfigs)); + setSaveDialogOpen(false); + setConfigName(""); + setEditingConfigId(null); + }; + + const editConfig = (config: SavedConfig) => { + setConfigName(config.name); + setSecretId(config.secretId); + setSecretKey(config.secretKey); + setZoneId(config.zoneId); + setEditingConfigId(config.id); + setSaveDialogOpen(true); + }; + + const loadConfig = (config: SavedConfig) => { + setSecretId(config.secretId); + setSecretKey(config.secretKey); + setZoneId(config.zoneId); + + toast({ + title: "成功", + description: "配置已加载", + }); + }; + + const deleteConfig = (id: string) => { + const updatedConfigs = savedConfigs.filter(config => config.id !== id); + setSavedConfigs(updatedConfigs); + localStorage.setItem("savedConfigs", JSON.stringify(updatedConfigs)); + + toast({ + title: "成功", + description: "配置已删除", + }); + }; + + const purgeUrls = async () => { + if (!urls.trim()) { + toast({ + variant: "destructive", + title: "错误", + description: "请输入需要刷新的URL", + }); + return; + } + + await callApi("purge_url", urls.split("\n").map(t => t.trim()).filter(t => t)); + }; + + const purgePrefixes = async () => { + if (!prefixes.trim()) { + toast({ + variant: "destructive", + title: "错误", + description: "请输入需要刷新的目录", + }); + return; + } + + await callApi("purge_prefix", prefixes.split("\n").map(t => t.trim()).filter(t => t)); + }; + + const purgeHosts = async () => { + if (!hosts.trim()) { + toast({ + variant: "destructive", + title: "错误", + description: "请输入需要刷新的主机", + }); + return; + } + + await callApi("purge_host", hosts.split("\n").map(t => t.trim()).filter(t => t)); + }; + + const purgeTags = async () => { + if (!tags.trim()) { + toast({ + variant: "destructive", + title: "错误", + description: "请输入需要刷新的缓存标签", + }); + return; + } + + await callApi("purge_cache_tag", tags.split("\n").map(t => t.trim()).filter(t => t)); + }; + + const purgeAll = async () => { + await callApi("purge_all", []); + }; + + const callApi = async (type: string, targets: string[], method = "invalidate") => { + if (!secretId || !secretKey || !zoneId) { + toast({ + variant: "destructive", + title: "错误", + description: "请填写完整的配置信息!", + }); + return; + } + + setLoading(true); + try { + const response = await fetch("/api/eo-cleancache", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + secretId, + secretKey, + zoneId, + type, + targets, + method, + }), + }); + + const result = await response.json(); + setResult(result); + setDialogOpen(true); + + if (result.Response && !result.Response.Error) { + toast({ + title: "成功", + description: "操作成功!", + }); + } else { + toast({ + variant: "destructive", + title: "失败", + description: `操作失败:${ + result.Response?.Error?.Message || "未知错误" + }`, + }); + } + } catch (error) { + toast({ + variant: "destructive", + title: "错误", + description: `请求失败:${(error as Error).message}`, + }); + setResult({ + Response: { + RequestId: "", + JobId: "", + FailedList: [], + Error: { + Message: (error as Error).message, + Code: "RequestError" + } + } + }); + setDialogOpen(true); + } finally { + setLoading(false); + } + }; + + return ( + + + 腾讯云EdgeOne缓存刷新工具 + + 数据保存在浏览器本地,不会上传到任何服务器。通用国内站和国际站。 + + + + + +
+
+ 已保存的配置 + 点击配置名称可快速加载 +
+ +
+
+ + {savedConfigs.length === 0 ? ( +

暂无保存的配置

+ ) : ( +
+ {savedConfigs.map((config) => ( +
+ +
+ + +
+
+ ))} +
+ )} +
+
+ +
+
+ + setSecretId(e.target.value)} + placeholder="请输入 SecretId" + /> +
+ + 国内站: https://console.cloud.tencent.com/cam/capi + + + 国际站: https://console.intl.cloud.tencent.com/cam/capi + +
+
+ +
+ + setSecretKey(e.target.value)} + placeholder="请输入 SecretKey" + /> +
+ +
+ + setZoneId(e.target.value)} + placeholder="请输入 ZoneId" + /> +
+ + 国内站: https://console.cloud.tencent.com/edgeone/zones + + + 国际站: https://console.intl.cloud.tencent.com/edgeone/zones + +
+
+
+ + +
+ + + 刷新全部 + + + URL刷新 + + + 目录刷新 + + + Host刷新 + + + Cache Tag + + +
+ + + + + 刷新全部缓存 + + 将清理该域名下的所有缓存文件 + + + + + + + + + + + + 按URL刷新 + + 输入需要刷新的URL地址,每行一个 + + + +