mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 13:48:55 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 825bcb08f4 | |||
| cd3b8fdf91 | |||
| 48ccde053b | |||
| d9ec7c362c |
@@ -36,4 +36,9 @@ export const deleteDomain = (id: number) => {
|
||||
// 更新一个域名(包括公开状态和配置信息)
|
||||
export const updateDomain = (id: number, data: { is_public: boolean, billing_data: BillingDataMod }) => {
|
||||
return fetcher<Domain>(FetcherMethod.PUT, `/api/v1/domains/${id}`, data)
|
||||
}
|
||||
|
||||
// 同步 Whois 信息
|
||||
export const syncDomainWHOIS = (id: number) => {
|
||||
return fetcher<Domain>(FetcherMethod.POST, `/api/v1/domains/${id}/sync`)
|
||||
}
|
||||
+121
-78
@@ -50,7 +50,7 @@ interface NotifierCardProps {
|
||||
|
||||
const notificationFormSchema = z.object({
|
||||
name: z.string().min(1),
|
||||
url: z.string().url(),
|
||||
url: z.string().min(1),
|
||||
request_method: z.coerce.number().int().min(1).max(255),
|
||||
request_type: z.coerce.number().int().min(1).max(255),
|
||||
request_header: z.string(),
|
||||
@@ -58,6 +58,7 @@ const notificationFormSchema = z.object({
|
||||
verify_tls: asOptionalField(z.boolean()),
|
||||
skip_check: asOptionalField(z.boolean()),
|
||||
format_metric_units: asOptionalField(z.boolean()),
|
||||
type: z.coerce.number().int().default(1),
|
||||
})
|
||||
|
||||
export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
@@ -77,6 +78,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
verify_tls: (data as any).verify_tls ?? false,
|
||||
skip_check: (data as any).skip_check ?? false,
|
||||
format_metric_units: (data as any).format_metric_units ?? false,
|
||||
type: data.type ?? 1,
|
||||
}
|
||||
: {
|
||||
name: "",
|
||||
@@ -88,6 +90,7 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
verify_tls: false,
|
||||
skip_check: false,
|
||||
format_metric_units: false,
|
||||
type: 1,
|
||||
},
|
||||
resetOptions: {
|
||||
keepDefaultValues: false,
|
||||
@@ -143,102 +146,123 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Notification Type</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
value={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
<SelectItem value="1">Webhook</SelectItem>
|
||||
<SelectItem value="2">SMTP (Email)</SelectItem>
|
||||
<SelectItem value="3">Telegram</SelectItem>
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="url"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>URL</FormLabel>
|
||||
<FormLabel>
|
||||
{form.watch("type") == 2 ? "SMTP Server (host:port)" :
|
||||
form.watch("type") == 3 ? "Bot Token" : "URL"}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Input {...field} />
|
||||
<Input {...field} placeholder={form.watch("type") == 3 ? "123456:ABC-DEF" : ""} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("RequestMethod")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Method" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestMethods).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{form.watch("type") != 2 && form.watch("type") != 3 && (
|
||||
<>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_method"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("RequestMethod")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Method" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestMethods).map(
|
||||
([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
),
|
||||
)}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_type"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("Type")}</FormLabel>
|
||||
<Select
|
||||
onValueChange={field.onChange}
|
||||
defaultValue={`${field.value}`}
|
||||
>
|
||||
<FormControl>
|
||||
<SelectTrigger>
|
||||
<SelectValue placeholder="Request Type" />
|
||||
</SelectTrigger>
|
||||
</FormControl>
|
||||
<SelectContent>
|
||||
{Object.entries(nrequestTypes).map(([k, v]) => (
|
||||
<SelectItem key={k} value={k}>
|
||||
{v}
|
||||
</SelectItem>
|
||||
))}
|
||||
</SelectContent>
|
||||
</Select>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_header"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("RequestHeader")}</FormLabel>
|
||||
<FormLabel>
|
||||
{form.watch("type") == 2 ? "SMTP User:Pass" :
|
||||
form.watch("type") == 3 ? "Chat ID" : t("RequestHeader")}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y"
|
||||
placeholder='{"User-Agent":"Nezha-Agent"}'
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_body"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("RequestBody")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className="resize-y h-[240px]"
|
||||
placeholder='{ "content":"#NEZHA#", "ServerName":"#SERVER.NAME#", "ServerIP":"#SERVER.IP#", "ServerIPV4":"#SERVER.IPV4#", "ServerIPV6":"#SERVER.IPV6#", "CPU":"#SERVER.CPU#", "MEM":"#SERVER.MEM#", "SWAP":"#SERVER.SWAP#", "DISK":"#SERVER.DISK#", "NetInSpeed":"#SERVER.NETINSPEED#", "NetOutSpeed":"#SERVER.NETOUTSPEED#", "TransferIn":"#SERVER.TRANSFERIN#", "TranferOut":"#SERVER.TRANSFEROUT#", "Load1":"#SERVER.LOAD1#", "Load5":"#SERVER.LOAD5#", "Load15":"#SERVER.LOAD15#", "TCP_CONN_COUNT":"#SERVER.TCPCONNCOUNT", "UDP_CONN_COUNT":"#SERVER.UDPCONNCOUNT" }'
|
||||
placeholder={
|
||||
form.watch("type") == 2 ? "user:pass" :
|
||||
form.watch("type") == 3 ? "123456789" : '{"User-Agent":"Nezha-Agent"}'
|
||||
}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
@@ -246,6 +270,25 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
{form.watch("type") != 3 && (
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="request_body"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{form.watch("type") == 2 ? "Recipient Email" : t("RequestBody")}</FormLabel>
|
||||
<FormControl>
|
||||
<Textarea
|
||||
className={form.watch("type") == 2 ? "resize-y" : "resize-y h-[240px]"}
|
||||
placeholder={form.watch("type") == 2 ? "target@example.com" : '...'}
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
)}
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="verify_tls"
|
||||
|
||||
@@ -65,6 +65,11 @@ const serverFormSchema = z.object({
|
||||
},
|
||||
),
|
||||
),
|
||||
billing_data: z.object({
|
||||
registrar: asOptionalField(z.string()),
|
||||
endDate: asOptionalField(z.string()),
|
||||
notes: asOptionalField(z.string()),
|
||||
}).optional(),
|
||||
})
|
||||
|
||||
export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
@@ -249,6 +254,35 @@ export const ServerCard: React.FC<ServerCardProps> = ({ data, mutate }) => {
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="p-3 border rounded-md border-dashed space-y-2">
|
||||
<Label className="text-xs text-muted-foreground uppercase font-bold">Billing & Expiry</Label>
|
||||
<FormField
|
||||
control={form.control as any}
|
||||
name="billing_data.registrar"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Registrar</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="AWS / Azure /阿里云" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control as any}
|
||||
name="billing_data.endDate"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Expiry Date</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="date" {...field} value={field.value?.split('T')[0] || ''} onChange={(e) => field.onChange(e.target.value ? new Date(e.target.value).toISOString() : '')} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
<FormField
|
||||
control={form.control as any}
|
||||
name="public_note"
|
||||
|
||||
@@ -151,6 +151,8 @@
|
||||
"AgentRealIPHeader": "Agent real IP request header",
|
||||
"UseDirectConnectingIP": "Use direct connection IP",
|
||||
"IPChangeNotification": "IP Change Notification",
|
||||
"IPChangeNotificationGroupID": "IP Change Notification Group ID",
|
||||
"ExpiryNotificationGroupID": "Expiry Notification Group ID",
|
||||
"FullIPNotification": "Show Full IP Address in Notification Messages",
|
||||
"EditService": "Edit Service",
|
||||
"CreateService": "Create Service",
|
||||
|
||||
@@ -157,7 +157,9 @@
|
||||
"WebRealIPHeader": "前端真实IP请求头",
|
||||
"AgentRealIPHeader": "Agent真实IP请求头",
|
||||
"UseDirectConnectingIP": "使用直连 IP",
|
||||
"IPChangeNotification": "IP变更通知",
|
||||
"IPChangeNotification": "IP 变更通知",
|
||||
"IPChangeNotificationGroupID": "IP 变更通知组 ID",
|
||||
"ExpiryNotificationGroupID": "到期通知组 ID",
|
||||
"FullIPNotification": "在通知消息中显示完整的 IP 地址",
|
||||
"LoginFailed": "登录失败",
|
||||
"BruteForceAttackingToken": "暴力攻击令牌",
|
||||
|
||||
+14
-2
@@ -1,7 +1,7 @@
|
||||
// src/routes/domain.tsx (最终 Bug 修复版)
|
||||
|
||||
import { useState, useEffect } from 'react'
|
||||
import { PlusCircle, RefreshCw, MoreVertical, Trash2, Edit, CheckCircle } from 'lucide-react'
|
||||
import { PlusCircle, RefreshCw, MoreVertical, Trash2, Edit, CheckCircle, RefreshCcw } from 'lucide-react'
|
||||
|
||||
// 导入 shadcn/ui 组件
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
@@ -19,7 +19,7 @@ import { toast } from 'sonner'
|
||||
|
||||
// 导入 API 类型和函数
|
||||
import type { Domain, BillingDataMod } from '@/types/api'
|
||||
import { useDomainList, addDomain, verifyDomain, deleteDomain, updateDomain } from '@/api/domain'
|
||||
import { useDomainList, addDomain, verifyDomain, deleteDomain, updateDomain, syncDomainWHOIS } from '@/api/domain'
|
||||
import useSWR from 'swr'
|
||||
|
||||
|
||||
@@ -83,6 +83,17 @@ export default function DomainPage() {
|
||||
}
|
||||
}
|
||||
|
||||
const handleSyncWhois = async (domainId: number) => {
|
||||
const loadingToast = toast.loading('正在同步 Whois 信息...')
|
||||
try {
|
||||
await syncDomainWHOIS(domainId)
|
||||
toast.success('同步成功', { id: loadingToast, description: '域名 Whois 信息已更新。' })
|
||||
mutate()
|
||||
} catch (err) {
|
||||
toast.error('同步失败', { id: loadingToast, description: (err as Error).message })
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async (domainId: number, domainName: string) => {
|
||||
if (window.confirm(`确定要删除域名 ${domainName} 吗?`)) {
|
||||
try {
|
||||
@@ -213,6 +224,7 @@ export default function DomainPage() {
|
||||
<DropdownMenuTrigger asChild><Button variant="ghost" size="icon"><MoreVertical className="h-4 w-4" /></Button></DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{domain.Status === 'pending' && (<DropdownMenuItem onClick={() => handleVerify(domain.ID)}><CheckCircle className="mr-2 h-4 w-4" /> 验证</DropdownMenuItem>)}
|
||||
{domain.Status === 'verified' && (<DropdownMenuItem onClick={() => handleSyncWhois(domain.ID)}><RefreshCcw className="mr-2 h-4 w-4" /> 同步 Whois</DropdownMenuItem>)}
|
||||
<DropdownMenuItem onClick={() => handleEditClick(domain)}><Edit className="mr-2 h-4 w-4" /> 编辑</DropdownMenuItem>
|
||||
<DropdownMenuItem className="text-red-600" onClick={() => handleDelete(domain.ID, domain.Domain)}><Trash2 className="mr-2 h-4 w-4" /> 删除</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
|
||||
@@ -131,6 +131,32 @@ export default function SettingsPage() {
|
||||
<div>
|
||||
<Form {...form}>
|
||||
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-2 my-2">
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="ip_change_notification_group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t("IPChangeNotificationGroupID")}</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="expiry_notification_group_id"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>Expiry Notification Group ID</FormLabel>
|
||||
<FormControl>
|
||||
<Input type="number" placeholder="Enter Group ID" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="site_name"
|
||||
|
||||
@@ -389,6 +389,7 @@ export interface ModelNotification {
|
||||
created_at?: string;
|
||||
id?: number;
|
||||
name: string;
|
||||
type?: number;
|
||||
request_body: string;
|
||||
request_header: string;
|
||||
request_method: number;
|
||||
@@ -401,6 +402,7 @@ export interface ModelNotification {
|
||||
export interface ModelNotificationForm {
|
||||
/** @minLength 1 */
|
||||
name?: string;
|
||||
type?: number;
|
||||
request_body?: string;
|
||||
request_header?: string;
|
||||
request_method?: number;
|
||||
@@ -464,6 +466,17 @@ export interface ModelProfileForm {
|
||||
reject_password?: boolean;
|
||||
}
|
||||
|
||||
export interface ModelBillingDataMod {
|
||||
registrar?: string;
|
||||
registeredDate?: string;
|
||||
endDate?: string;
|
||||
renewalPrice?: string;
|
||||
autoRenewal?: string;
|
||||
notes?: string;
|
||||
cycle?: string;
|
||||
amount?: string;
|
||||
}
|
||||
|
||||
export interface ModelRule {
|
||||
/** 覆盖范围 RuleCoverAll/IgnoreAll */
|
||||
cover: number;
|
||||
@@ -519,6 +532,7 @@ export interface ModelServer {
|
||||
/** 公开备注 */
|
||||
public_note?: string;
|
||||
state?: ModelHostState;
|
||||
billing_data?: ModelBillingDataMod;
|
||||
updated_at?: string;
|
||||
uuid?: string;
|
||||
}
|
||||
@@ -546,6 +560,7 @@ export interface ModelServerForm {
|
||||
override_ddns_domains?: Record<string, string[]>;
|
||||
/** 公开备注 */
|
||||
public_note?: string;
|
||||
billing_data?: ModelBillingDataMod;
|
||||
}
|
||||
|
||||
export interface ModelServerGroup {
|
||||
@@ -655,6 +670,7 @@ export interface ModelSetting {
|
||||
enable_ip_change_notification?: boolean;
|
||||
/** 通知信息IP不打码 */
|
||||
enable_plain_ip_in_notification?: boolean;
|
||||
expiry_notification_group_id?: number;
|
||||
/** 特定服务器IP(多个服务器用逗号分隔) */
|
||||
ignored_ip_notification?: string;
|
||||
ignored_ip_notification_server_ids?: Record<string, boolean>;
|
||||
@@ -684,6 +700,7 @@ export interface ModelSettingForm {
|
||||
dns_servers?: string;
|
||||
enable_ip_change_notification?: boolean;
|
||||
enable_plain_ip_in_notification?: boolean;
|
||||
expiry_notification_group_id?: number;
|
||||
ignored_ip_notification?: string;
|
||||
install_host?: string;
|
||||
/** IP变更提醒的通知组 */
|
||||
|
||||
Reference in New Issue
Block a user