mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-18 09:31:55 +08:00
Merge pull request #8 from woodchen-ink:hamster1963-main
Hamster1963-main
This commit is contained in:
commit
388458fc06
39
package.json
39
package.json
@ -16,55 +16,56 @@
|
|||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
"@radix-ui/react-accordion": "^1.2.2",
|
"@radix-ui/react-accordion": "^1.2.2",
|
||||||
"@radix-ui/react-checkbox": "^1.1.3",
|
"@radix-ui/react-checkbox": "^1.1.3",
|
||||||
"@radix-ui/react-dialog": "^1.1.4",
|
"@radix-ui/react-dialog": "^1.1.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.1.4",
|
"@radix-ui/react-dropdown-menu": "^2.1.5",
|
||||||
"@radix-ui/react-label": "^2.1.1",
|
"@radix-ui/react-label": "^2.1.1",
|
||||||
"@radix-ui/react-popover": "^1.1.4",
|
"@radix-ui/react-popover": "^1.1.5",
|
||||||
"@radix-ui/react-progress": "^1.1.1",
|
"@radix-ui/react-progress": "^1.1.1",
|
||||||
"@radix-ui/react-separator": "^1.1.1",
|
"@radix-ui/react-separator": "^1.1.1",
|
||||||
"@radix-ui/react-slot": "^1.1.1",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.1.2",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@tanstack/react-query": "^5.63.0",
|
"@tanstack/react-query": "^5.64.2",
|
||||||
"@tanstack/react-query-devtools": "^5.63.0",
|
"@tanstack/react-query-devtools": "^5.64.2",
|
||||||
"@tanstack/react-table": "^8.20.6",
|
"@tanstack/react-table": "^8.20.6",
|
||||||
"@trivago/prettier-plugin-sort-imports": "^5.2.1",
|
"@trivago/prettier-plugin-sort-imports": "^5.2.1",
|
||||||
"@types/d3-geo": "^3.1.0",
|
"@types/d3-geo": "^3.1.0",
|
||||||
"@types/luxon": "^3.4.2",
|
"@types/luxon": "^3.4.2",
|
||||||
"class-variance-authority": "^0.7.1",
|
"class-variance-authority": "^0.7.1",
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"country-flag-icons": "^1.5.13",
|
"cmdk": "1.0.0",
|
||||||
|
"country-flag-icons": "^1.5.14",
|
||||||
"d3-geo": "^3.1.1",
|
"d3-geo": "^3.1.1",
|
||||||
"dayjs": "^1.11.13",
|
"dayjs": "^1.11.13",
|
||||||
"framer-motion": "^12.0.0-alpha.2",
|
"framer-motion": "^12.0.3",
|
||||||
"i18n-iso-countries": "^7.13.0",
|
"i18n-iso-countries": "^7.13.0",
|
||||||
"i18next": "^24.2.1",
|
"i18next": "^24.2.1",
|
||||||
"lucide-react": "^0.460.0",
|
"lucide-react": "^0.460.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.9",
|
"prettier-plugin-tailwindcss": "^0.6.10",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-i18next": "^15.4.0",
|
"react-i18next": "^15.4.0",
|
||||||
"react-router-dom": "^7.1.1",
|
"react-router-dom": "^7.1.3",
|
||||||
"recharts": "^2.15.0",
|
"recharts": "^2.15.0",
|
||||||
"sonner": "^1.7.1",
|
"sonner": "^1.7.2",
|
||||||
"tailwind-merge": "^2.6.0",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwindcss-animate": "^1.0.7"
|
"tailwindcss-animate": "^1.0.7"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.17.0",
|
"@eslint/js": "^9.18.0",
|
||||||
"@types/node": "^22.10.5",
|
"@types/node": "^22.10.9",
|
||||||
"@types/react": "^19.0.4",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^19.0.2",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.7.2",
|
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||||
"autoprefixer": "^10.4.20",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^9.17.0",
|
"eslint": "^9.18.0",
|
||||||
"eslint-plugin-react-hooks": "^5.1.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.16",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"postcss": "^8.4.49",
|
"postcss": "^8.5.1",
|
||||||
"tailwindcss": "^3.4.17",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "~5.6.3",
|
"typescript": "~5.6.3",
|
||||||
"typescript-eslint": "^8.19.1",
|
"typescript-eslint": "^8.21.0",
|
||||||
"vite": "^6.0.7"
|
"vite": "^6.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@ import ErrorPage from "./pages/ErrorPage"
|
|||||||
import NotFound from "./pages/NotFound"
|
import NotFound from "./pages/NotFound"
|
||||||
import Server from "./pages/Server"
|
import Server from "./pages/Server"
|
||||||
import ServerDetail from "./pages/ServerDetail"
|
import ServerDetail from "./pages/ServerDetail"
|
||||||
|
import { DashCommand } from "./components/DashCommand"
|
||||||
|
|
||||||
const App: React.FC = () => {
|
const App: React.FC = () => {
|
||||||
const { data: settingData, error } = useQuery({
|
const { data: settingData, error } = useQuery({
|
||||||
@ -62,9 +63,7 @@ const App: React.FC = () => {
|
|||||||
i18n.changeLanguage(settingData?.data?.config?.language)
|
i18n.changeLanguage(settingData?.data?.config?.language)
|
||||||
}
|
}
|
||||||
|
|
||||||
const customMobileBackgroundImage =
|
const customMobileBackgroundImage = window.CustomMobileBackgroundImage !== "" ? window.CustomMobileBackgroundImage : undefined
|
||||||
// @ts-expect-error CustomMobileBackgroundImage is a global variable
|
|
||||||
(window.CustomMobileBackgroundImage as string) !== "" ? window.CustomMobileBackgroundImage : undefined
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Router basename={import.meta.env.BASE_URL}>
|
<Router basename={import.meta.env.BASE_URL}>
|
||||||
@ -92,6 +91,7 @@ const App: React.FC = () => {
|
|||||||
<main className="flex z-20 min-h-[calc(100vh-calc(var(--spacing)*16))] flex-1 flex-col gap-4 p-4 md:p-10 md:pt-8">
|
<main className="flex z-20 min-h-[calc(100vh-calc(var(--spacing)*16))] flex-1 flex-col gap-4 p-4 md:p-10 md:pt-8">
|
||||||
<RefreshToast />
|
<RefreshToast />
|
||||||
<Header />
|
<Header />
|
||||||
|
<DashCommand />
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path="/" element={<Server />} />
|
<Route path="/" element={<Server />} />
|
||||||
<Route path="/server/:id" element={<ServerDetail />} />
|
<Route path="/server/:id" element={<ServerDetail />} />
|
||||||
|
114
src/components/DashCommand.tsx
Normal file
114
src/components/DashCommand.tsx
Normal file
@ -0,0 +1,114 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { CommandDialog, CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandSeparator } from "@/components/ui/command"
|
||||||
|
import { useTheme } from "@/hooks/use-theme"
|
||||||
|
import { useWebSocketContext } from "@/hooks/use-websocket-context"
|
||||||
|
import { formatNezhaInfo } from "@/lib/utils"
|
||||||
|
import { NezhaWebsocketResponse } from "@/types/nezha-api"
|
||||||
|
import { Home, Moon, Sun, SunMoon } from "lucide-react"
|
||||||
|
import { useEffect, useState } from "react"
|
||||||
|
import { useTranslation } from "react-i18next"
|
||||||
|
import { useNavigate } from "react-router-dom"
|
||||||
|
|
||||||
|
export function DashCommand() {
|
||||||
|
const [open, setOpen] = useState(false)
|
||||||
|
const [search, setSearch] = useState("")
|
||||||
|
const navigate = useNavigate()
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { setTheme } = useTheme()
|
||||||
|
|
||||||
|
const { lastMessage, connected } = useWebSocketContext()
|
||||||
|
|
||||||
|
const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const down = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === "k" && (e.metaKey || e.ctrlKey)) {
|
||||||
|
e.preventDefault()
|
||||||
|
setOpen((open) => !open)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("keydown", down)
|
||||||
|
return () => document.removeEventListener("keydown", down)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
if (!connected || !nezhaWsData) return null
|
||||||
|
|
||||||
|
const shortcuts = [
|
||||||
|
{
|
||||||
|
keywords: ["home", "homepage"],
|
||||||
|
icon: <Home />,
|
||||||
|
label: t("Home"),
|
||||||
|
action: () => navigate("/"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keywords: ["light", "theme", "lightmode"],
|
||||||
|
icon: <Sun />,
|
||||||
|
label: t("ToggleLightMode"),
|
||||||
|
action: () => setTheme("light"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keywords: ["dark", "theme", "darkmode"],
|
||||||
|
icon: <Moon />,
|
||||||
|
label: t("ToggleDarkMode"),
|
||||||
|
action: () => setTheme("dark"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
keywords: ["system", "theme", "systemmode"],
|
||||||
|
icon: <SunMoon />,
|
||||||
|
label: t("ToggleSystemMode"),
|
||||||
|
action: () => setTheme("system"),
|
||||||
|
},
|
||||||
|
].map((item) => ({
|
||||||
|
...item,
|
||||||
|
value: `${item.keywords.join(" ")} ${item.label}`,
|
||||||
|
}))
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
|
<CommandInput placeholder={t("TypeCommand")} value={search} onValueChange={setSearch} />
|
||||||
|
<CommandList>
|
||||||
|
<CommandEmpty>{t("NoResults")}</CommandEmpty>
|
||||||
|
<CommandGroup heading={t("Servers")}>
|
||||||
|
{nezhaWsData.servers.map((server) => (
|
||||||
|
<CommandItem
|
||||||
|
key={server.id}
|
||||||
|
value={server.name}
|
||||||
|
onSelect={() => {
|
||||||
|
navigate(`/server/${server.id}`)
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{formatNezhaInfo(nezhaWsData.now, server).online ? (
|
||||||
|
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500 self-center" />
|
||||||
|
) : (
|
||||||
|
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500 self-center" />
|
||||||
|
)}
|
||||||
|
<span>{server.name}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
<CommandSeparator />
|
||||||
|
|
||||||
|
<CommandGroup heading={t("Shortcuts")}>
|
||||||
|
{shortcuts.map((item) => (
|
||||||
|
<CommandItem
|
||||||
|
key={item.label}
|
||||||
|
value={item.value}
|
||||||
|
onSelect={() => {
|
||||||
|
item.action()
|
||||||
|
setOpen(false)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{item.icon}
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</CommandList>
|
||||||
|
</CommandDialog>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -41,6 +41,8 @@ function Header() {
|
|||||||
// @ts-expect-error CustomDesc is a global variable
|
// @ts-expect-error CustomDesc is a global variable
|
||||||
const customDesc = window.CustomDesc || t("nezha")
|
const customDesc = window.CustomDesc || t("nezha")
|
||||||
|
|
||||||
|
const customMobileBackgroundImage = window.CustomMobileBackgroundImage !== "" ? window.CustomMobileBackgroundImage : undefined
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const link = document.querySelector("link[rel*='icon']") || document.createElement("link")
|
const link = document.querySelector("link[rel*='icon']") || document.createElement("link")
|
||||||
// @ts-expect-error set link.type
|
// @ts-expect-error set link.type
|
||||||
@ -109,6 +111,7 @@ function Header() {
|
|||||||
onClick={handleBackgroundToggle}
|
onClick={handleBackgroundToggle}
|
||||||
className={cn("rounded-full px-[9px] bg-white dark:bg-black", {
|
className={cn("rounded-full px-[9px] bg-white dark:bg-black", {
|
||||||
"bg-white/70 dark:bg-black/70": customBackgroundImage,
|
"bg-white/70 dark:bg-black/70": customBackgroundImage,
|
||||||
|
"hidden sm:block": customMobileBackgroundImage,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
<ImageMinus className="w-4 h-4" />
|
<ImageMinus className="w-4 h-4" />
|
||||||
|
107
src/components/ui/command.tsx
Normal file
107
src/components/ui/command.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Dialog, DialogContent } from "@/components/ui/dialog"
|
||||||
|
import { cn } from "@/lib/utils"
|
||||||
|
import { type DialogProps, DialogTitle } from "@radix-ui/react-dialog"
|
||||||
|
import { Command as CommandPrimitive } from "cmdk"
|
||||||
|
import { Search } from "lucide-react"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
|
const Command = React.forwardRef<React.ElementRef<typeof CommandPrimitive>, React.ComponentPropsWithoutRef<typeof CommandPrimitive>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive
|
||||||
|
ref={ref}
|
||||||
|
className={cn("flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground", className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
Command.displayName = CommandPrimitive.displayName
|
||||||
|
|
||||||
|
const CommandDialog = ({ children, ...props }: DialogProps) => {
|
||||||
|
return (
|
||||||
|
<Dialog {...props}>
|
||||||
|
<DialogTitle />
|
||||||
|
<DialogContent className="overflow-hidden p-0 shadow-lg">
|
||||||
|
<Command className="[&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-4 [&_[cmdk-input-wrapper]_svg]:w-4 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-4 [&_[cmdk-item]_svg]:w-4">
|
||||||
|
{children}
|
||||||
|
</Command>
|
||||||
|
</DialogContent>
|
||||||
|
</Dialog>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const CommandInput = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Input>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<div className="flex items-center border-b bg-stone-100 dark:bg-stone-900 px-3" cmdk-input-wrapper="">
|
||||||
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
|
<CommandPrimitive.Input
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CommandInput.displayName = CommandPrimitive.Input.displayName
|
||||||
|
|
||||||
|
const CommandList = React.forwardRef<React.ElementRef<typeof CommandPrimitive.List>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.List ref={ref} className={cn("max-h-[300px] mb-1 overflow-y-auto overflow-x-hidden", className)} {...props} />
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CommandList.displayName = CommandPrimitive.List.displayName
|
||||||
|
|
||||||
|
const CommandEmpty = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Empty>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>>(
|
||||||
|
(props, ref) => <CommandPrimitive.Empty ref={ref} className="py-6 text-center text-sm" {...props} />,
|
||||||
|
)
|
||||||
|
|
||||||
|
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
||||||
|
|
||||||
|
const CommandGroup = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Group>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Group
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
||||||
|
|
||||||
|
const CommandSeparator = React.forwardRef<
|
||||||
|
React.ElementRef<typeof CommandPrimitive.Separator>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
||||||
|
>(({ className, ...props }, ref) => <CommandPrimitive.Separator ref={ref} className={cn("-mx-1 h-px bg-border", className)} {...props} />)
|
||||||
|
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
||||||
|
|
||||||
|
const CommandItem = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Item>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>>(
|
||||||
|
({ className, ...props }, ref) => (
|
||||||
|
<CommandPrimitive.Item
|
||||||
|
ref={ref}
|
||||||
|
className={cn(
|
||||||
|
"relative flex cursor-default gap-2 select-none items-center rounded-[8px] px-2 py-1.5 text-xs outline-none data-[disabled=true]:pointer-events-none data-[selected='true']:bg-stone-100 dark:data-[selected='true']:bg-stone-900 data-[selected=true]:text-accent-foreground data-[disabled=true]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
CommandItem.displayName = CommandPrimitive.Item.displayName
|
||||||
|
|
||||||
|
const CommandShortcut = ({ className, ...props }: React.HTMLAttributes<HTMLSpanElement>) => {
|
||||||
|
return <span className={cn("ml-auto text-xs tracking-widest text-muted-foreground", className)} {...props} />
|
||||||
|
}
|
||||||
|
CommandShortcut.displayName = "CommandShortcut"
|
||||||
|
|
||||||
|
export { Command, CommandDialog, CommandInput, CommandList, CommandEmpty, CommandGroup, CommandItem, CommandShortcut, CommandSeparator }
|
@ -3,6 +3,7 @@ import { useEffect, useState } from "react"
|
|||||||
declare global {
|
declare global {
|
||||||
interface Window {
|
interface Window {
|
||||||
CustomBackgroundImage: string
|
CustomBackgroundImage: string
|
||||||
|
CustomMobileBackgroundImage: string
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,5 +116,13 @@
|
|||||||
"price": "Price",
|
"price": "Price",
|
||||||
"free": "Free",
|
"free": "Free",
|
||||||
"usage-baseed": "Usage-based"
|
"usage-baseed": "Usage-based"
|
||||||
}
|
},
|
||||||
|
"TypeCommand": "Type a command or search...",
|
||||||
|
"NoResults": "No results found.",
|
||||||
|
"Servers": "Servers",
|
||||||
|
"Shortcuts": "Shortcuts",
|
||||||
|
"ToggleLightMode": "Toggle Light Mode",
|
||||||
|
"ToggleDarkMode": "Toggle Dark Mode",
|
||||||
|
"ToggleSystemMode": "Toggle System Mode",
|
||||||
|
"Home": "Home"
|
||||||
}
|
}
|
||||||
|
120
src/locales/ta/translation.json
Normal file
120
src/locales/ta/translation.json
Normal file
@ -0,0 +1,120 @@
|
|||||||
|
{
|
||||||
|
"nezha": "கண்காணிப்பு",
|
||||||
|
"overview": "கண்ணோட்டம்",
|
||||||
|
"dashboard": "முகப்புப்பெட்டி",
|
||||||
|
"login": "புகுபதிவு",
|
||||||
|
"serverCard": {
|
||||||
|
"mem": "மெம்",
|
||||||
|
"stg": "Stg",
|
||||||
|
"days": "நாட்கள்",
|
||||||
|
"hours": "மணி",
|
||||||
|
"upload": "பதிவேற்றும்",
|
||||||
|
"download": "பதிவிறக்கம்",
|
||||||
|
"system": "மண்டலம்",
|
||||||
|
"uptime": "நேரம்",
|
||||||
|
"totalUpload": "பதிவேற்றும்",
|
||||||
|
"totalDownload": "பதிவிறக்கம்"
|
||||||
|
},
|
||||||
|
"online": "ஆன்லைனில்",
|
||||||
|
"offline": "இணையமில்லாமல்",
|
||||||
|
"whereTheTimeIs": "நேரம் இருக்கும் இடம்",
|
||||||
|
"refreshing": "புத்துணர்ச்சி",
|
||||||
|
"info": {
|
||||||
|
"websocketConnecting": "வெப்சாக்கெட் இணைத்தல்",
|
||||||
|
"websocketConnected": "வெப்சாக்கெட் இணைக்கப்பட்டுள்ளது",
|
||||||
|
"websocketDisconnected": "வெப்சாக்கெட் துண்டிக்கப்பட்டது",
|
||||||
|
"processing": "செயலாக்கம் ..."
|
||||||
|
},
|
||||||
|
"serverOverview": {
|
||||||
|
"totalServers": "மொத்த சேவையகங்கள்",
|
||||||
|
"onlineServers": "நிகழ்நிலை சேவையகங்கள்",
|
||||||
|
"offlineServers": "இணைப்பில்லாத சேவையகங்கள்",
|
||||||
|
"totalBandwidth": "மொத்த அலைவரிசை",
|
||||||
|
"speed": "வேகம்",
|
||||||
|
"network": "பிணையம்"
|
||||||
|
},
|
||||||
|
"map": {
|
||||||
|
"Distributions": "சேவையகங்கள் விநியோகிக்கப்படுகின்றன",
|
||||||
|
"Regions": "பகுதிகள்",
|
||||||
|
"Servers": "சேவையகங்கள்"
|
||||||
|
},
|
||||||
|
"cycleTransfer": {
|
||||||
|
"used": "பயன்படுத்தப்பட்டது",
|
||||||
|
"total": "மொத்தம்",
|
||||||
|
"nextUpdate": "அடுத்த புதுப்பிப்பு"
|
||||||
|
},
|
||||||
|
"serverDetail": {
|
||||||
|
"offline": "இணையமில்லாமல்",
|
||||||
|
"unknown": "தெரியவில்லை",
|
||||||
|
"uptime": "நேரம்",
|
||||||
|
"version": "பதிப்பு",
|
||||||
|
"arch": "மான்",
|
||||||
|
"mem": "மெம்",
|
||||||
|
"disk": "வட்டு",
|
||||||
|
"region": "பகுதி",
|
||||||
|
"system": "மண்டலம்",
|
||||||
|
"status": "நிலை",
|
||||||
|
"online": "ஆன்லைனில்",
|
||||||
|
"days": "நாட்கள்",
|
||||||
|
"upload": "பதிவேற்றும்",
|
||||||
|
"download": "பதிவிறக்கம்",
|
||||||
|
"lastActive": "கடைசி செயலில் நேரம்",
|
||||||
|
"temperature": "வெப்பநிலை"
|
||||||
|
},
|
||||||
|
"serverDetailChart": {
|
||||||
|
"swap": "இடமாற்றம்",
|
||||||
|
"upload": "பதிவேற்றும்",
|
||||||
|
"download": "பதிவிறக்கம்",
|
||||||
|
"process": "செயல்முறை",
|
||||||
|
"disk": "வட்டு",
|
||||||
|
"mem": "மெம்"
|
||||||
|
},
|
||||||
|
"footer": {
|
||||||
|
"themeBy": "மூலம் கருப்பொருள் "
|
||||||
|
},
|
||||||
|
"language": {
|
||||||
|
"zh-CN": "எளிமைப்படுத்தப்பட்ட சீன",
|
||||||
|
"zh-TW": "பாரம்பரிய சீன",
|
||||||
|
"en-US": "ஆங்கிலம்"
|
||||||
|
},
|
||||||
|
"theme": {
|
||||||
|
"light": "ஒளி",
|
||||||
|
"dark": "இருண்ட",
|
||||||
|
"system": "மண்டலம்"
|
||||||
|
},
|
||||||
|
"error": {
|
||||||
|
"pageNotFound": "பக்கம் கிடைக்கவில்லை",
|
||||||
|
"backToHome": "வீட்டிற்கு திரும்பவும்"
|
||||||
|
},
|
||||||
|
"tabSwitch": {
|
||||||
|
"Detail": "விவரம்",
|
||||||
|
"Network": "பிணையம்"
|
||||||
|
},
|
||||||
|
"monitor": {
|
||||||
|
"noData": "சேவையக மானிட்டர் தரவு இல்லை, முதலில் ஒரு பணி மானிட்டரைச் சேர்க்கவும்",
|
||||||
|
"avgDelay": "சுணக்கம்",
|
||||||
|
"monitorCount": "சேவைகள்"
|
||||||
|
},
|
||||||
|
"pwa": {
|
||||||
|
"offlineReady": "ஆஃப்லைனில் வேலை செய்ய பயன்பாடு தயாராக உள்ளது",
|
||||||
|
"newContent": "புதிய உள்ளடக்கம் கிடைக்கிறது",
|
||||||
|
"reload": "புதுப்பிப்பு"
|
||||||
|
},
|
||||||
|
"billingInfo": {
|
||||||
|
"remaining": "மீதமுள்ள",
|
||||||
|
"error": "பிழை",
|
||||||
|
"indefinite": "காலவரையற்றது",
|
||||||
|
"expired": "காலாவதியான",
|
||||||
|
"days": "நாட்கள்",
|
||||||
|
"price": "விலை",
|
||||||
|
"free": "இலவசம்",
|
||||||
|
"usage-baseed": "பயன்பாடு அடிப்படையிலானது"
|
||||||
|
},
|
||||||
|
"serviceTracker": {
|
||||||
|
"noService": "பணி தரவு இல்லை",
|
||||||
|
"uptime": "நேரம்",
|
||||||
|
"daysAgo": "சில நாட்களுக்கு முன்பு",
|
||||||
|
"today": "இன்று",
|
||||||
|
"loading": "ஏற்றுகிறது ..."
|
||||||
|
}
|
||||||
|
}
|
@ -116,5 +116,13 @@
|
|||||||
"price": "价格",
|
"price": "价格",
|
||||||
"free": "免费",
|
"free": "免费",
|
||||||
"usage-baseed": "按量计费"
|
"usage-baseed": "按量计费"
|
||||||
}
|
},
|
||||||
|
"TypeCommand": "输入命令或搜索",
|
||||||
|
"NoResults": "结果为空",
|
||||||
|
"Servers": "服务器",
|
||||||
|
"Shortcuts": "快捷键",
|
||||||
|
"ToggleLightMode": "切换亮色模式",
|
||||||
|
"ToggleDarkMode": "切换暗色模式",
|
||||||
|
"ToggleSystemMode": "切换系统模式",
|
||||||
|
"Home": "首页"
|
||||||
}
|
}
|
||||||
|
@ -112,5 +112,13 @@
|
|||||||
"price": "價格",
|
"price": "價格",
|
||||||
"free": "免費",
|
"free": "免費",
|
||||||
"usage-baseed": "按量計費"
|
"usage-baseed": "按量計費"
|
||||||
}
|
},
|
||||||
|
"TypeCommand": "輸入命令或搜尋",
|
||||||
|
"NoResults": "沒有結果",
|
||||||
|
"Servers": "伺服器",
|
||||||
|
"Shortcuts": "快捷鍵",
|
||||||
|
"ToggleLightMode": "切換亮色模式",
|
||||||
|
"ToggleDarkMode": "切換暗色模式",
|
||||||
|
"ToggleSystemMode": "切換系統模式",
|
||||||
|
"Home": "首頁"
|
||||||
}
|
}
|
||||||
|
@ -72,7 +72,16 @@ export default function Servers() {
|
|||||||
restoreScrollPosition()
|
restoreScrollPosition()
|
||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
const groupTabs = ["All", ...(groupData?.data?.map((item: ServerGroup) => item.group.name) || [])]
|
const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null
|
||||||
|
|
||||||
|
const groupTabs = [
|
||||||
|
"All",
|
||||||
|
...(groupData?.data
|
||||||
|
?.filter((item: ServerGroup) => {
|
||||||
|
return Array.isArray(item.servers) && item.servers.some((serverId) => nezhaWsData?.servers?.some((server) => server.id === serverId))
|
||||||
|
})
|
||||||
|
?.map((item: ServerGroup) => item.group.name) || []),
|
||||||
|
]
|
||||||
|
|
||||||
if (!connected && !lastMessage) {
|
if (!connected && !lastMessage) {
|
||||||
return (
|
return (
|
||||||
@ -85,8 +94,6 @@ export default function Servers() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const nezhaWsData = lastMessage ? (JSON.parse(lastMessage.data) as NezhaWebsocketResponse) : null
|
|
||||||
|
|
||||||
if (!nezhaWsData) {
|
if (!nezhaWsData) {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center ">
|
<div className="flex flex-col items-center justify-center ">
|
||||||
|
Loading…
x
Reference in New Issue
Block a user