mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-05-06 05:38:51 +00:00
feat: refactor notification UI and remove background image
This commit is contained in:
@@ -51,3 +51,8 @@ export const updateDomain = (
|
|||||||
export const syncDomainWHOIS = (id: number) => {
|
export const syncDomainWHOIS = (id: number) => {
|
||||||
return fetcher<Domain>(FetcherMethod.POST, `/api/v1/domains/${id}/sync`)
|
return fetcher<Domain>(FetcherMethod.POST, `/api/v1/domains/${id}/sync`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 同步所有域名
|
||||||
|
export const syncAllDomains = () => {
|
||||||
|
return fetcher<any>(FetcherMethod.POST, "/api/v1/domains/sync-all")
|
||||||
|
}
|
||||||
|
|||||||
+25
-27
@@ -55,9 +55,9 @@ const notificationFormSchema = z.object({
|
|||||||
request_type: z.coerce.number().int().min(1).max(255),
|
request_type: z.coerce.number().int().min(1).max(255),
|
||||||
request_header: z.string(),
|
request_header: z.string(),
|
||||||
request_body: z.string(),
|
request_body: z.string(),
|
||||||
verify_tls: asOptionalField(z.boolean()),
|
verify_tls: z.boolean().default(false),
|
||||||
skip_check: asOptionalField(z.boolean()),
|
skip_check: z.boolean().default(false),
|
||||||
format_metric_units: asOptionalField(z.boolean()),
|
format_metric_units: z.boolean().default(false),
|
||||||
type: z.coerce.number().int().default(1),
|
type: z.coerce.number().int().default(1),
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -75,9 +75,9 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
|||||||
request_type: data.request_type ?? 1,
|
request_type: data.request_type ?? 1,
|
||||||
request_header: data.request_header ?? "",
|
request_header: data.request_header ?? "",
|
||||||
request_body: data.request_body ?? "",
|
request_body: data.request_body ?? "",
|
||||||
verify_tls: (data as any).verify_tls ?? false,
|
verify_tls: data.verify_tls ?? false,
|
||||||
skip_check: (data as any).skip_check ?? false,
|
skip_check: data.skip_check ?? false,
|
||||||
format_metric_units: (data as any).format_metric_units ?? false,
|
format_metric_units: data.format_metric_units ?? false,
|
||||||
type: data.type ?? 1,
|
type: data.type ?? 1,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
@@ -319,23 +319,25 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
<div className="pt-4 border-t space-y-3">
|
||||||
|
<Label className="text-xs font-semibold text-muted-foreground uppercase tracking-wider">
|
||||||
|
{t("AdvancedSettings")}
|
||||||
|
</Label>
|
||||||
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-x-4 gap-y-2">
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="verify_tls"
|
name="verify_tls"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex items-center space-x-2">
|
<FormItem className="flex items-center space-x-2 space-y-0 py-1">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<Label className="text-sm">
|
|
||||||
{t("VerifyTLS")}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormLabel className="text-sm font-normal cursor-pointer">
|
||||||
|
{t("VerifyTLS")}
|
||||||
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -343,19 +345,16 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="skip_check"
|
name="skip_check"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex items-center space-x-2">
|
<FormItem className="flex items-center space-x-2 space-y-0 py-1">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<Label className="text-sm">
|
|
||||||
{t("DoNotSendTestMessage")}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormLabel className="text-sm font-normal cursor-pointer">
|
||||||
|
{t("DoNotSendTestMessage")}
|
||||||
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
@@ -363,22 +362,21 @@ export const NotifierCard: React.FC<NotifierCardProps> = ({ data, mutate }) => {
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="format_metric_units"
|
name="format_metric_units"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem className="flex items-center space-x-2">
|
<FormItem className="flex items-center space-x-2 space-y-0 py-1">
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<Checkbox
|
<Checkbox
|
||||||
checked={field.value}
|
checked={field.value}
|
||||||
onCheckedChange={field.onChange}
|
onCheckedChange={field.onChange}
|
||||||
/>
|
/>
|
||||||
<Label className="text-sm">
|
|
||||||
{t("FormatMetricUnits")}
|
|
||||||
</Label>
|
|
||||||
</div>
|
|
||||||
</FormControl>
|
</FormControl>
|
||||||
<FormMessage />
|
<FormLabel className="text-sm font-normal cursor-pointer">
|
||||||
|
{t("FormatMetricUnits")}
|
||||||
|
</FormLabel>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<DialogFooter className="justify-end">
|
<DialogFooter className="justify-end">
|
||||||
<DialogClose asChild>
|
<DialogClose asChild>
|
||||||
<Button type="button" className="my-2" variant="secondary">
|
<Button type="button" className="my-2" variant="secondary">
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { createContext, useContext, useEffect, useState } from "react"
|
import { createContext, useContext, useEffect, useState } from "react"
|
||||||
|
import { DateTime } from "luxon"
|
||||||
|
|
||||||
export type Theme = "dark" | "light" | "system"
|
export type Theme = "dark" | "light" | "system"
|
||||||
|
|
||||||
@@ -30,22 +31,28 @@ export function ThemeProvider({
|
|||||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const [hour, setHour] = useState(() => DateTime.now().hour)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setInterval(() => {
|
||||||
|
setHour(DateTime.now().hour)
|
||||||
|
}, 60000)
|
||||||
|
return () => clearInterval(timer)
|
||||||
|
}, [])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const root = window.document.documentElement
|
const root = window.document.documentElement
|
||||||
|
|
||||||
root.classList.remove("light", "dark")
|
root.classList.remove("light", "dark")
|
||||||
|
|
||||||
|
let effectiveTheme = theme
|
||||||
if (theme === "system") {
|
if (theme === "system") {
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
|
const isNight = hour >= 18 || hour < 6
|
||||||
? "dark"
|
effectiveTheme = isNight ? "dark" : "light"
|
||||||
: "light"
|
|
||||||
|
|
||||||
root.classList.add(systemTheme)
|
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
|
||||||
root.classList.add(theme)
|
root.classList.add(effectiveTheme)
|
||||||
}, [theme])
|
}, [theme, hour])
|
||||||
|
|
||||||
const value = {
|
const value = {
|
||||||
theme,
|
theme,
|
||||||
|
|||||||
+15
-3
@@ -2,6 +2,7 @@
|
|||||||
import {
|
import {
|
||||||
addDomain,
|
addDomain,
|
||||||
deleteDomain,
|
deleteDomain,
|
||||||
|
syncAllDomains,
|
||||||
syncDomainWHOIS,
|
syncDomainWHOIS,
|
||||||
updateDomain,
|
updateDomain,
|
||||||
useDomainList,
|
useDomainList,
|
||||||
@@ -73,6 +74,7 @@ export default function DomainPage() {
|
|||||||
data: domainData,
|
data: domainData,
|
||||||
error,
|
error,
|
||||||
mutate,
|
mutate,
|
||||||
|
isValidating,
|
||||||
} = useSWR("/api/v1/domains", useDomainList, { revalidateOnFocus: false })
|
} = useSWR("/api/v1/domains", useDomainList, { revalidateOnFocus: false })
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -86,6 +88,16 @@ export default function DomainPage() {
|
|||||||
}
|
}
|
||||||
}, [domainData, error])
|
}, [domainData, error])
|
||||||
|
|
||||||
|
const handleRefreshAll = async () => {
|
||||||
|
try {
|
||||||
|
await syncAllDomains()
|
||||||
|
toast.success("刷新成功", { description: "已触发所有域名的状态同步。" })
|
||||||
|
mutate()
|
||||||
|
} catch (err) {
|
||||||
|
toast.error("刷新失败", { description: (err as Error).message })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const handleAddDomain = async () => {
|
const handleAddDomain = async () => {
|
||||||
if (!newDomainName) {
|
if (!newDomainName) {
|
||||||
toast.error("请输入域名")
|
toast.error("请输入域名")
|
||||||
@@ -219,10 +231,10 @@ export default function DomainPage() {
|
|||||||
<Button
|
<Button
|
||||||
variant="outline"
|
variant="outline"
|
||||||
size="icon"
|
size="icon"
|
||||||
onClick={() => mutate()}
|
onClick={handleRefreshAll}
|
||||||
disabled={isLoading}
|
disabled={isValidating}
|
||||||
>
|
>
|
||||||
<RefreshCw className={`h-4 w-4 ${isLoading ? "animate-spin" : ""}`} />
|
<RefreshCw className={`h-4 w-4 ${isValidating ? "animate-spin" : ""}`} />
|
||||||
</Button>
|
</Button>
|
||||||
<Dialog open={isAddModalOpen} onOpenChange={setIsAddModalOpen}>
|
<Dialog open={isAddModalOpen} onOpenChange={setIsAddModalOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
|
|||||||
+6
-3
@@ -4,7 +4,8 @@ import { Toaster } from "@/components/ui/sonner"
|
|||||||
import useSetting from "@/hooks/useSetting"
|
import useSetting from "@/hooks/useSetting"
|
||||||
import i18n from "@/lib/i18n"
|
import i18n from "@/lib/i18n"
|
||||||
import { InjectContext } from "@/lib/inject"
|
import { InjectContext } from "@/lib/inject"
|
||||||
import { useEffect } from "react"
|
import { DateTime } from "luxon"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Outlet } from "react-router-dom"
|
import { Outlet } from "react-router-dom"
|
||||||
|
|
||||||
@@ -36,8 +37,10 @@ export default function Root() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
<ThemeProvider defaultTheme="system" storageKey="vite-ui-theme">
|
||||||
<section className="text-sm mx-auto h-full flex flex-col justify-between">
|
<section
|
||||||
<div>
|
className="text-sm mx-auto h-full flex flex-col justify-between relative z-10 bg-background"
|
||||||
|
>
|
||||||
|
<div className="flex-1">
|
||||||
<Header />
|
<Header />
|
||||||
<div className="max-w-5xl mx-auto">
|
<div className="max-w-5xl mx-auto">
|
||||||
<Outlet />
|
<Outlet />
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ const settingFormSchema = z.object({
|
|||||||
custom_links: asOptionalField(z.string()),
|
custom_links: asOptionalField(z.string()),
|
||||||
background_image_day: asOptionalField(z.string()),
|
background_image_day: asOptionalField(z.string()),
|
||||||
background_image_night: asOptionalField(z.string()),
|
background_image_night: asOptionalField(z.string()),
|
||||||
|
telegram_bot_token: asOptionalField(z.string()),
|
||||||
|
telegram_admin_chat_id: asOptionalField(z.string()),
|
||||||
})
|
})
|
||||||
|
|
||||||
export default function SettingsPage() {
|
export default function SettingsPage() {
|
||||||
@@ -251,6 +253,32 @@ export default function SettingsPage() {
|
|||||||
</FormItem>
|
</FormItem>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="telegram_bot_token"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Telegram Bot Token</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="123456789:ABCDEF..." {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="telegram_admin_chat_id"
|
||||||
|
render={({ field }) => (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Telegram Admin Chat ID</FormLabel>
|
||||||
|
<FormControl>
|
||||||
|
<Input placeholder="12345678" {...field} />
|
||||||
|
</FormControl>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
<FormField
|
<FormField
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="language"
|
name="language"
|
||||||
|
|||||||
Reference in New Issue
Block a user