mirror of
https://github.com/woodchen-ink/nezha-dash-v1.git
synced 2025-07-18 09:31:55 +08:00
Merge pull request #10 from woodchen-ink:hamster1963-main
Hamster1963-main
This commit is contained in:
commit
df8a72f76a
68
package.json
68
package.json
@ -12,6 +12,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/inter": "^5.1.1",
|
"@fontsource/inter": "^5.1.1",
|
||||||
"@heroicons/react": "^2.2.0",
|
"@heroicons/react": "^2.2.0",
|
||||||
|
"@number-flow/react": "^0.5.5",
|
||||||
"@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.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
@ -20,14 +21,12 @@
|
|||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@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.0.2",
|
"@radix-ui/react-slot": "^1.1.1",
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
"@radix-ui/react-switch": "^1.1.2",
|
||||||
"@radix-ui/react-tabs": "^1.0.4",
|
"@tanstack/react-query": "^5.65.1",
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
"@tanstack/react-query-devtools": "^5.65.1",
|
||||||
"@tanstack/react-query": "^5.14.2",
|
|
||||||
"@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.2",
|
||||||
"@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.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
@ -36,39 +35,36 @@
|
|||||||
"country-flag-icons": "^1.5.14",
|
"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.3",
|
"framer-motion": "^12.0.6",
|
||||||
"i18n-iso-countries": "^7.13.0",
|
"i18n-iso-countries": "^7.13.0",
|
||||||
"i18next": "^23.7.11",
|
"i18next": "^24.2.2",
|
||||||
"lucide-react": "^0.302.0",
|
"lucide-react": "^0.460.0",
|
||||||
"luxon": "^3.5.0",
|
"luxon": "^3.5.0",
|
||||||
"prettier-plugin-tailwindcss": "^0.6.10",
|
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||||
"react": "^18.2.0",
|
"react": "^19.0.0",
|
||||||
"react-day-picker": "^8.10.0",
|
"react-dom": "^19.0.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-i18next": "^15.4.0",
|
||||||
"react-i18next": "^14.0.0",
|
"react-router-dom": "^7.1.3",
|
||||||
"react-router-dom": "^6.21.1",
|
"recharts": "^2.15.1",
|
||||||
"recharts": "^2.15.0",
|
"sonner": "^1.7.3",
|
||||||
"sonner": "^1.7.2",
|
"tailwind-merge": "^2.6.0",
|
||||||
"tailwind-merge": "^2.2.0",
|
"tailwindcss-animate": "^1.0.7"
|
||||||
"tailwindcss-animate": "^1.0.7",
|
|
||||||
"use-debounce": "^10.0.0",
|
|
||||||
"zod": "^3.22.4"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/js": "^9.18.0",
|
"@eslint/js": "^9.19.0",
|
||||||
"@types/node": "^20.10.5",
|
"@types/node": "^22.12.0",
|
||||||
"@types/react": "^18.2.43",
|
"@types/react": "^19.0.8",
|
||||||
"@types/react-dom": "^18.2.17",
|
"@types/react-dom": "^19.0.3",
|
||||||
"@vitejs/plugin-react-swc": "^3.5.0",
|
"@vitejs/plugin-react-swc": "^3.7.2",
|
||||||
"autoprefixer": "^10.4.16",
|
"autoprefixer": "^10.4.20",
|
||||||
"eslint": "^8.55.0",
|
"eslint": "^9.19.0",
|
||||||
"eslint-plugin-react-hooks": "^4.6.0",
|
"eslint-plugin-react-hooks": "^5.1.0",
|
||||||
"eslint-plugin-react-refresh": "^0.4.5",
|
"eslint-plugin-react-refresh": "^0.4.18",
|
||||||
"globals": "^15.14.0",
|
"globals": "^15.14.0",
|
||||||
"postcss": "^8.4.32",
|
"postcss": "^8.5.1",
|
||||||
"tailwindcss": "^3.4.0",
|
"tailwindcss": "^3.4.17",
|
||||||
"typescript": "^5.2.2",
|
"typescript": "~5.6.3",
|
||||||
"typescript-eslint": "^8.21.0",
|
"typescript-eslint": "^8.22.0",
|
||||||
"vite": "^5.0.8"
|
"vite": "^6.0.11"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ import React, { useEffect, useState } from "react"
|
|||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { Route, BrowserRouter as Router, Routes } from "react-router-dom"
|
import { Route, BrowserRouter as Router, Routes } from "react-router-dom"
|
||||||
|
|
||||||
|
import { DashCommand } from "./components/DashCommand"
|
||||||
import ErrorBoundary from "./components/ErrorBoundary"
|
import ErrorBoundary from "./components/ErrorBoundary"
|
||||||
import Footer from "./components/Footer"
|
import Footer from "./components/Footer"
|
||||||
import Header, { RefreshToast } from "./components/Header"
|
import Header, { RefreshToast } from "./components/Header"
|
||||||
@ -15,7 +16,6 @@ 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({
|
||||||
|
@ -69,27 +69,31 @@ export function DashCommand() {
|
|||||||
<>
|
<>
|
||||||
<CommandDialog open={open} onOpenChange={setOpen}>
|
<CommandDialog open={open} onOpenChange={setOpen}>
|
||||||
<CommandInput placeholder={t("TypeCommand")} value={search} onValueChange={setSearch} />
|
<CommandInput placeholder={t("TypeCommand")} value={search} onValueChange={setSearch} />
|
||||||
<CommandList>
|
<CommandList className="border-t">
|
||||||
<CommandEmpty>{t("NoResults")}</CommandEmpty>
|
<CommandEmpty>{t("NoResults")}</CommandEmpty>
|
||||||
<CommandGroup heading={t("Servers")}>
|
{nezhaWsData.servers && nezhaWsData.servers.length > 0 && (
|
||||||
{nezhaWsData.servers.map((server) => (
|
<>
|
||||||
<CommandItem
|
<CommandGroup heading={t("Servers")}>
|
||||||
key={server.id}
|
{nezhaWsData.servers.map((server) => (
|
||||||
value={server.name}
|
<CommandItem
|
||||||
onSelect={() => {
|
key={server.id}
|
||||||
navigate(`/server/${server.id}`)
|
value={server.name}
|
||||||
setOpen(false)
|
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" />
|
>
|
||||||
) : (
|
{formatNezhaInfo(nezhaWsData.now, server).online ? (
|
||||||
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500 self-center" />
|
<span className="h-2 w-2 shrink-0 rounded-full bg-green-500 self-center" />
|
||||||
)}
|
) : (
|
||||||
<span>{server.name}</span>
|
<span className="h-2 w-2 shrink-0 rounded-full bg-red-500 self-center" />
|
||||||
</CommandItem>
|
)}
|
||||||
))}
|
<span>{server.name}</span>
|
||||||
</CommandGroup>
|
</CommandItem>
|
||||||
|
))}
|
||||||
|
</CommandGroup>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<CommandSeparator />
|
<CommandSeparator />
|
||||||
|
|
||||||
<CommandGroup heading={t("Shortcuts")}>
|
<CommandGroup heading={t("Shortcuts")}>
|
||||||
|
@ -5,6 +5,7 @@ import { useBackground } from "@/hooks/use-background"
|
|||||||
import { useWebSocketContext } from "@/hooks/use-websocket-context"
|
import { useWebSocketContext } from "@/hooks/use-websocket-context"
|
||||||
import { fetchLoginUser, fetchSetting } from "@/lib/nezha-api"
|
import { fetchLoginUser, fetchSetting } from "@/lib/nezha-api"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import NumberFlow, { NumberFlowGroup } from "@number-flow/react"
|
||||||
import { useQuery } from "@tanstack/react-query"
|
import { useQuery } from "@tanstack/react-query"
|
||||||
import { AnimatePresence, m } from "framer-motion"
|
import { AnimatePresence, m } from "framer-motion"
|
||||||
import { ImageMinus } from "lucide-react"
|
import { ImageMinus } from "lucide-react"
|
||||||
@ -266,31 +267,35 @@ function DashboardLink() {
|
|||||||
|
|
||||||
function Overview() {
|
function Overview() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [mouted, setMounted] = useState(false)
|
const [time, setTime] = useState({
|
||||||
|
hh: DateTime.now().setLocale("en-US").hour,
|
||||||
|
mm: DateTime.now().setLocale("en-US").minute,
|
||||||
|
ss: DateTime.now().setLocale("en-US").second,
|
||||||
|
})
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setMounted(true)
|
const timer = setInterval(() => {
|
||||||
}, [])
|
setTime({
|
||||||
const timeOption = DateTime.TIME_WITH_SECONDS
|
hh: DateTime.now().setLocale("en-US").hour,
|
||||||
timeOption.hour12 = true
|
mm: DateTime.now().setLocale("en-US").minute,
|
||||||
const [timeString, setTimeString] = useState(DateTime.now().setLocale("en-US").toLocaleString(timeOption))
|
ss: DateTime.now().setLocale("en-US").second,
|
||||||
useEffect(() => {
|
})
|
||||||
const updateTime = () => {
|
}, 1000)
|
||||||
const now = DateTime.now().setLocale("en-US").toLocaleString(timeOption)
|
|
||||||
setTimeString(now)
|
return () => clearInterval(timer)
|
||||||
requestAnimationFrame(updateTime)
|
|
||||||
}
|
|
||||||
requestAnimationFrame(updateTime)
|
|
||||||
}, [])
|
}, [])
|
||||||
return (
|
return (
|
||||||
<section className={"mt-10 flex flex-col md:mt-16 header-timer"}>
|
<section className={"mt-10 flex flex-col md:mt-16 header-timer"}>
|
||||||
<p className="text-base font-semibold">👋 {t("overview")}</p>
|
<p className="text-base font-semibold">👋 {t("overview")}</p>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<p className="text-sm font-medium opacity-50">{t("whereTheTimeIs")}</p>
|
<p className="text-sm font-medium opacity-50">{t("whereTheTimeIs")}</p>
|
||||||
{mouted ? (
|
<NumberFlowGroup>
|
||||||
<p className="text-sm font-medium">{timeString}</p>
|
<div style={{ fontVariantNumeric: "tabular-nums" }} className="flex text-sm font-medium mt-0.5">
|
||||||
) : (
|
<NumberFlow trend={1} value={time.hh} format={{ minimumIntegerDigits: 2 }} />
|
||||||
<Skeleton className="h-[20px] w-[50px] rounded-[5px] bg-muted-foreground/10 animate-none"></Skeleton>
|
<NumberFlow prefix=":" trend={1} value={time.mm} digits={{ 1: { max: 5 } }} format={{ minimumIntegerDigits: 2 }} />
|
||||||
)}
|
<NumberFlow prefix=":" trend={1} value={time.ss} digits={{ 1: { max: 5 } }} format={{ minimumIntegerDigits: 2 }} />
|
||||||
|
</div>
|
||||||
|
</NumberFlowGroup>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@/components/ui/dropdown-menu"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import { CheckCircleIcon } from "@heroicons/react/20/solid"
|
import { CheckCircleIcon, LanguageIcon } from "@heroicons/react/20/solid"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
|
|
||||||
export function LanguageSwitcher() {
|
export function LanguageSwitcher() {
|
||||||
@ -34,7 +34,7 @@ export function LanguageSwitcher() {
|
|||||||
"bg-white/70 dark:bg-black/70": customBackgroundImage,
|
"bg-white/70 dark:bg-black/70": customBackgroundImage,
|
||||||
})}
|
})}
|
||||||
>
|
>
|
||||||
{localeItems.find((item) => item.code === locale)?.name}
|
<LanguageIcon className="size-4" />
|
||||||
<span className="sr-only">Change language</span>
|
<span className="sr-only">Change language</span>
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
@ -33,7 +33,7 @@ const CommandDialog = ({ children, ...props }: DialogProps) => {
|
|||||||
|
|
||||||
const CommandInput = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Input>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>>(
|
const CommandInput = React.forwardRef<React.ElementRef<typeof CommandPrimitive.Input>, React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>>(
|
||||||
({ className, ...props }, ref) => (
|
({ className, ...props }, ref) => (
|
||||||
<div className="flex items-center border-b bg-stone-100 dark:bg-stone-900 px-3" cmdk-input-wrapper="">
|
<div className="flex items-center bg-stone-100 dark:bg-stone-900 px-3" cmdk-input-wrapper="">
|
||||||
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
<Search className="mr-2 h-4 w-4 shrink-0 opacity-50" />
|
||||||
<CommandPrimitive.Input
|
<CommandPrimitive.Input
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user