From e5682aacbd6210cc83281ba0ff4326ba29624613 Mon Sep 17 00:00:00 2001 From: hamster1963 <1410514192@qq.com> Date: Fri, 22 Nov 2024 22:20:38 +0800 Subject: [PATCH] update: init --- .github/workflows/Build.yml | 55 +++++++ .gitignore | 24 +++ README.md | 1 + bun.lockb | Bin 0 -> 135901 bytes components.json | 20 +++ eslint.config.js | 28 ++++ index.html | 13 ++ package.json | 54 +++++++ postcss.config.js | 6 + public/apple-touch-icon.png | Bin 0 -> 4385 bytes src/App.tsx | 28 ++++ src/assets/apple-touch-icon.png | Bin 0 -> 2291 bytes src/assets/react.svg | 1 + src/components/Footer.tsx | 19 +++ src/components/Header.tsx | 89 +++++++++++ src/components/ThemeProvider.tsx | 65 ++++++++ src/components/ThemeSwitcher.tsx | 62 ++++++++ src/components/loading/Loader.tsx | 13 ++ src/components/ui/button.tsx | 56 +++++++ src/components/ui/checkbox.tsx | 28 ++++ src/components/ui/dialog.tsx | 120 ++++++++++++++ src/components/ui/dropdown-menu.tsx | 198 +++++++++++++++++++++++ src/components/ui/input.tsx | 25 +++ src/components/ui/label.tsx | 24 +++ src/components/ui/separator.tsx | 29 ++++ src/components/ui/skeleton.tsx | 15 ++ src/components/ui/table.tsx | 117 ++++++++++++++ src/hooks/use-theme.ts | 12 ++ src/index.css | 234 ++++++++++++++++++++++++++++ src/lib/nav-router.ts | 30 ++++ src/lib/nezha-api.ts | 80 ++++++++++ src/lib/nezha-model.ts | 79 ++++++++++ src/lib/useWebsocket.tsx | 89 +++++++++++ src/lib/utils.ts | 6 + src/lib/websocketProvider.tsx | 26 ++++ src/main.tsx | 25 +++ src/pages/Server.tsx | 22 +++ src/vite-env.d.ts | 1 + tailwind.config.js | 59 +++++++ tsconfig.app.json | 30 ++++ tsconfig.app.tsbuildinfo | 1 + tsconfig.json | 13 ++ tsconfig.node.json | 23 +++ tsconfig.node.tsbuildinfo | 1 + vite.config.ts | 13 ++ 45 files changed, 1834 insertions(+) create mode 100644 .github/workflows/Build.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100755 bun.lockb create mode 100644 components.json create mode 100644 eslint.config.js create mode 100644 index.html create mode 100644 package.json create mode 100644 postcss.config.js create mode 100644 public/apple-touch-icon.png create mode 100644 src/App.tsx create mode 100644 src/assets/apple-touch-icon.png create mode 100644 src/assets/react.svg create mode 100644 src/components/Footer.tsx create mode 100644 src/components/Header.tsx create mode 100644 src/components/ThemeProvider.tsx create mode 100644 src/components/ThemeSwitcher.tsx create mode 100644 src/components/loading/Loader.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/hooks/use-theme.ts create mode 100644 src/index.css create mode 100644 src/lib/nav-router.ts create mode 100644 src/lib/nezha-api.ts create mode 100644 src/lib/nezha-model.ts create mode 100644 src/lib/useWebsocket.tsx create mode 100644 src/lib/utils.ts create mode 100644 src/lib/websocketProvider.tsx create mode 100644 src/main.tsx create mode 100644 src/pages/Server.tsx create mode 100644 src/vite-env.d.ts create mode 100644 tailwind.config.js create mode 100644 tsconfig.app.json create mode 100644 tsconfig.app.tsbuildinfo create mode 100644 tsconfig.json create mode 100644 tsconfig.node.json create mode 100644 tsconfig.node.tsbuildinfo create mode 100644 vite.config.ts diff --git a/.github/workflows/Build.yml b/.github/workflows/Build.yml new file mode 100644 index 0000000..cba4a7c --- /dev/null +++ b/.github/workflows/Build.yml @@ -0,0 +1,55 @@ +name: Build and release static export + +on: + push: + tags: + - "v*" + +jobs: + release: + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Check out code + uses: actions/checkout@v4 + + - name: Set up Bun + uses: oven-sh/setup-bun@v1 + with: + bun-version: "latest" + + - name: Install dependencies + run: bun install + + - name: Build static export + run: | + bun run build --base=/dashboard + + - name: Compress dist folder + run: zip -r dist.zip dist + + - name: Release + uses: softprops/action-gh-release@v2 + with: + files: dist.zip + + changelog: + needs: release + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set node + uses: actions/setup-node@v4 + with: + registry-url: https://registry.npmjs.org/ + node-version: lts/* + + - run: npx changelogithub + env: + GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/README.md b/README.md new file mode 100644 index 0000000..fe5e5ce --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Nezha-Dashboard diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..2c56be6e300598eb5dca90e06ccb82bc0afc35dd GIT binary patch literal 135901 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 BvUUIf literal 0 HcmV?d00001 diff --git a/components.json b/components.json new file mode 100644 index 0000000..10d70eb --- /dev/null +++ b/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "stone", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + } +} diff --git a/eslint.config.js b/eslint.config.js new file mode 100644 index 0000000..79a552e --- /dev/null +++ b/eslint.config.js @@ -0,0 +1,28 @@ +import js from "@eslint/js"; +import globals from "globals"; +import reactHooks from "eslint-plugin-react-hooks"; +import reactRefresh from "eslint-plugin-react-refresh"; +import tseslint from "typescript-eslint"; + +export default tseslint.config( + { ignores: ["dist"] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ["**/*.{ts,tsx}"], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + "react-hooks": reactHooks, + "react-refresh": reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + "react-refresh/only-export-components": [ + "warn", + { allowConstantExport: true }, + ], + }, + }, +); diff --git a/index.html b/index.html new file mode 100644 index 0000000..e4b78ea --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + Vite + React + TS + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..3e2c1c0 --- /dev/null +++ b/package.json @@ -0,0 +1,54 @@ +{ + "name": "nazha-dashboard-vite", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@fontsource/inter": "^5.1.0", + "@heroicons/react": "^2.2.0", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slot": "^1.1.0", + "@tanstack/react-query": "^5.59.16", + "@tanstack/react-query-devtools": "^5.59.16", + "@tanstack/react-table": "^8.20.5", + "@types/luxon": "^3.4.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "framer-motion": "^11.11.10", + "lucide-react": "^0.453.0", + "luxon": "^3.5.0", + "react": "^18.3.1", + "react-dom": "^18.3.1", + "react-router-dom": "^6.27.0", + "sonner": "^1.5.0", + "tailwind-merge": "^2.5.4", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/node": "^22.8.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react-swc": "^3.7.1", + "autoprefixer": "^10.4.20", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "postcss": "^8.4.47", + "tailwindcss": "^3.4.14", + "typescript": "~5.6.3", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.10" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/public/apple-touch-icon.png b/public/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..e9c77083eead84ae3b56cdcee5a4bd9fa2289ae3 GIT binary patch literal 4385 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 a1>}yo`Y&O*b literal 0 HcmV?d00001 diff --git a/src/App.tsx b/src/App.tsx new file mode 100644 index 0000000..68dfb28 --- /dev/null +++ b/src/App.tsx @@ -0,0 +1,28 @@ +import React from "react"; +import { BrowserRouter as Router, Routes, Route } from "react-router-dom"; +import Header from "./components/Header"; +import Footer from "./components/Footer"; +import Server from "./pages/Server"; + +const App: React.FC = () => { + return ( + +
+
+
+ + + } + /> + +
+
+
+
+ ); +}; + +export default App; diff --git a/src/assets/apple-touch-icon.png b/src/assets/apple-touch-icon.png new file mode 100644 index 0000000000000000000000000000000000000000..5e07a895c41c5ce5e16c5d4332dec90e161940c4 GIT binary patch literal 2291 zcmdUx`%{x=7RQs97ZPGg4L2#5V7Uc^5?c^3B&d-<(I%uqA<9+lDqet;5I~WTSHOTu zpk1u%C4@8;aV2S7EO$t1R}lg8wjdWHY1)w{5Q-^AL1^g4&d&Y?yEEG#p7WVA-+AVo zneUnNJT**4yaUD^gFql065fy9W99AF2W@AKlo7l4tUz-1#8VOShn}+t1X`95yCb>*}HRU_W{1Sr_VJx2Ty%`r79w> z4JW-HGPmvW+qvB^O>p)LDSC&9u5gb1@K^P{6 zobQOovM$EzJai7(9;SsG$9Q7+B2W&<6F0$sd0nx<94Izw)5y!ok|D42Z|MjXtvWY* zKc|wfOXX{dj)en~A(RTmFQpvv>$p2(=g$;+*CEOcWz6&tb0VbVVxP=pwp|3{s#JOv zk-uU0Hx!B5PKye4zmUPY4!Yt3y!f0x&LKV9gi zMn=||4uxa@>WgV7Xoew3xk_(}J0ArtZh2KpGi2GQLrIBs%l#qBJ;01`KtwYP@C(*V zpQ&1$qF9x@5#pY>zPj-r{5Pjot~qxy{T$tF0I`eYcbkS)IMdOM3lPr2WtIR{%d@c+ zmwg*RHT*; zLpG?%ua8YIhgd9DwsBnM8FQ+nhP2YlAQv_;_tQBBJ#Dv_i8-^`KCbyM^*Nc*D0&d$=Qu4v7l$x3M} z$Y2O07l3zU`;R(3uEYncUH-Vtj~8z5-10##^$L7&ri&VUn~HKY-N&Ij&fK8qIGt=I zr{+|p7`6ik%b))3(xv@N3)2uf{_q^7^rfO~^`*r1IF&#!o6X@#Nl80%e`;a;OdJd- z3Y{y)xU7`ZlXI#Ve(RMDRu9(lY@PmmZu}`m=D*lv>2x=DUei%mQ&S_6lcJ-C)-0<6 zlD>t}&e1d4k59cfwz$F7b+V_J$l-8yii+XEvy^oe*rKc8=k8puTa9s6 z;$l!8)(P*B`HqiP^Qn<{^tyH&)6mZS=Sg=f8>(d;K2C_z6M%ic-h#3X{`Ub0j6pSmYm=|^?lb2aM&;KkLy4i@&O_fxuihkLLdI_RD?F{w zut+u3_a?srJ?+6MB50ohdA9O-9=-p`mHl0Sr3?JrhbeURw#f$sJz`^jI(BN;mmyzS zkW8UZYN0kLvdFa?)7e_;7xaB;LN*g1@QI|L-_#F9=T7LnRcX`FZZnyjtci} + ); +}; + +export default Footer; diff --git a/src/components/Header.tsx b/src/components/Header.tsx new file mode 100644 index 0000000..457581e --- /dev/null +++ b/src/components/Header.tsx @@ -0,0 +1,89 @@ +"use client"; + +// import { LanguageSwitcher } from "@/components/LanguageSwitcher"; +import { ModeToggle } from "@/components/ThemeSwitcher"; +import { Separator } from "@/components/ui/separator"; +import { Skeleton } from "@/components/ui/skeleton"; +import { DateTime } from "luxon"; +import { useEffect, useRef, useState } from "react"; + +function Header() { + + return ( +
+
+
+
+ apple-touch-icon +
+ {"NezhaDash"} + +

+ 哪吒监控面板 +

+
+
+ {/* */} + +
+
+ +
+ ); +} + +// https://github.com/streamich/react-use/blob/master/src/useInterval.ts +const useInterval = (callback: () => void, delay: number | null) => { + const savedCallback = useRef<() => void>(() => { }); + useEffect(() => { + savedCallback.current = callback; + }); + useEffect(() => { + if (delay !== null) { + const interval = setInterval(() => savedCallback.current(), delay || 0); + return () => clearInterval(interval); + } + return undefined; + }, [delay]); +}; +function Overview() { + const [mouted, setMounted] = useState(false); + useEffect(() => { + setMounted(true); + }, []); + const timeOption = DateTime.TIME_SIMPLE; + timeOption.hour12 = true; + const [timeString, setTimeString] = useState( + DateTime.now().setLocale("en-US").toLocaleString(timeOption), + ); + useInterval(() => { + setTimeString(DateTime.now().setLocale("en-US").toLocaleString(timeOption)); + }, 1000); + return ( +
+

👋 Overview

+
+

+ where the time is +

+ {mouted ? ( +

{timeString}

+ ) : ( + + )} +
+
+ ); +} +export default Header; diff --git a/src/components/ThemeProvider.tsx b/src/components/ThemeProvider.tsx new file mode 100644 index 0000000..0eb39bf --- /dev/null +++ b/src/components/ThemeProvider.tsx @@ -0,0 +1,65 @@ +import { createContext, useEffect, useState, ReactNode } from "react"; + +export type Theme = "dark" | "light" | "system"; + +type ThemeProviderProps = { + children: ReactNode; + defaultTheme?: Theme; + storageKey?: string; +}; + +type ThemeProviderState = { + theme: Theme; + setTheme: (theme: Theme) => void; +}; + +const initialState: ThemeProviderState = { + theme: "system", + setTheme: () => null, +}; + +const ThemeProviderContext = createContext(initialState); + +export function ThemeProvider({ + children, + defaultTheme = "system", + storageKey = "vite-ui-theme", +}: ThemeProviderProps) { + const [theme, setTheme] = useState( + () => (localStorage.getItem(storageKey) as Theme) || defaultTheme, + ); + + useEffect(() => { + const root = window.document.documentElement; + + root.classList.remove("light", "dark"); + + if (theme === "system") { + const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") + .matches + ? "dark" + : "light"; + + root.classList.add(systemTheme); + return; + } + + root.classList.add(theme); + }, [theme]); + + const value = { + theme, + setTheme: (theme: Theme) => { + localStorage.setItem(storageKey, theme); + setTheme(theme); + }, + }; + + return ( + + {children} + + ); +} + +export { ThemeProviderContext }; diff --git a/src/components/ThemeSwitcher.tsx b/src/components/ThemeSwitcher.tsx new file mode 100644 index 0000000..86a7dab --- /dev/null +++ b/src/components/ThemeSwitcher.tsx @@ -0,0 +1,62 @@ +"use client"; + +import { Button } from "@/components/ui/button"; +import { + DropdownMenu, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuTrigger, +} from "@/components/ui/dropdown-menu"; +import { cn } from "@/lib/utils"; +import { CheckCircleIcon } from "@heroicons/react/20/solid"; +import { Moon, Sun } from "lucide-react"; +import { Theme } from "@/components/ThemeProvider"; +import { useTheme } from "../hooks/use-theme"; + +export function ModeToggle() { + const { setTheme, theme } = useTheme(); + + const handleSelect = (e: Event, newTheme: Theme) => { + e.preventDefault(); + setTheme(newTheme); + }; + + return ( + + + + + + handleSelect(e, "light")} + > + Light + {theme === "light" && } + + handleSelect(e, "dark")} + > + Dark + {theme === "dark" && } + + handleSelect(e, "system")} + > + System + {theme === "system" && } + + + + ); +} diff --git a/src/components/loading/Loader.tsx b/src/components/loading/Loader.tsx new file mode 100644 index 0000000..d8f2108 --- /dev/null +++ b/src/components/loading/Loader.tsx @@ -0,0 +1,13 @@ +const bars = Array(8).fill(0); + +export const Loader = ({ visible }: { visible: boolean }) => { + return ( +
+
+ {bars.map((_, i) => ( +
+ ))} +
+
+ ); +}; diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx new file mode 100644 index 0000000..5373829 --- /dev/null +++ b/src/components/ui/button.tsx @@ -0,0 +1,56 @@ +import * as React from "react"; +import { Slot } from "@radix-ui/react-slot"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const buttonVariants = cva( + "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0", + { + variants: { + variant: { + default: "bg-primary text-primary-foreground hover:bg-primary/90", + destructive: + "bg-destructive text-destructive-foreground hover:bg-destructive/90", + outline: + "border border-input bg-background hover:bg-accent hover:text-accent-foreground", + secondary: + "bg-secondary text-secondary-foreground hover:bg-secondary/80", + ghost: "hover:bg-accent hover:text-accent-foreground", + link: "text-primary underline-offset-4 hover:underline", + }, + size: { + default: "h-10 px-4 py-2", + sm: "h-9 rounded-md px-3", + lg: "h-11 rounded-md px-8", + icon: "h-10 w-10", + }, + }, + defaultVariants: { + variant: "default", + size: "default", + }, + }, +); + +export interface ButtonProps + extends React.ButtonHTMLAttributes, + VariantProps { + asChild?: boolean; +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button"; + return ( + + ); + }, +); +Button.displayName = "Button"; + +export { Button, buttonVariants }; diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx new file mode 100644 index 0000000..58d1768 --- /dev/null +++ b/src/components/ui/checkbox.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import * as CheckboxPrimitive from "@radix-ui/react-checkbox"; +import { Check } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Checkbox = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + + +)); +Checkbox.displayName = CheckboxPrimitive.Root.displayName; + +export { Checkbox }; diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..775c93a --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,120 @@ +import * as React from "react"; +import * as DialogPrimitive from "@radix-ui/react-dialog"; +import { X } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = "DialogHeader"; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = "DialogFooter"; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..e209caa --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,198 @@ +import * as React from "react"; +import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"; +import { Check, ChevronRight, Circle } from "lucide-react"; + +import { cn } from "@/lib/utils"; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, children, ...props }, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, sideOffset = 4, ...props }, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, checked, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean; + } +>(({ className, inset, ...props }, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = "DropdownMenuShortcut"; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx new file mode 100644 index 0000000..13c4c87 --- /dev/null +++ b/src/components/ui/input.tsx @@ -0,0 +1,25 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +export interface InputProps + extends React.InputHTMLAttributes {} + +const Input = React.forwardRef( + ({ className, type, ...props }, ref) => { + return ( + + ); + }, +); +Input.displayName = "Input"; + +export { Input }; diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx new file mode 100644 index 0000000..44912af --- /dev/null +++ b/src/components/ui/label.tsx @@ -0,0 +1,24 @@ +import * as React from "react"; +import * as LabelPrimitive from "@radix-ui/react-label"; +import { cva, type VariantProps } from "class-variance-authority"; + +import { cn } from "@/lib/utils"; + +const labelVariants = cva( + "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70", +); + +const Label = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & + VariantProps +>(({ className, ...props }, ref) => ( + +)); +Label.displayName = LabelPrimitive.Root.displayName; + +export { Label }; diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx new file mode 100644 index 0000000..6d7f122 --- /dev/null +++ b/src/components/ui/separator.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as SeparatorPrimitive from "@radix-ui/react-separator" + +import { cn } from "@/lib/utils" + +const Separator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>( + ( + { className, orientation = "horizontal", decorative = true, ...props }, + ref + ) => ( + + ) +) +Separator.displayName = SeparatorPrimitive.Root.displayName + +export { Separator } diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx new file mode 100644 index 0000000..01b8b6d --- /dev/null +++ b/src/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@/lib/utils" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..73cf6b4 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from "react"; + +import { cn } from "@/lib/utils"; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+ + +)); +Table.displayName = "Table"; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableHeader.displayName = "TableHeader"; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableBody.displayName = "TableBody"; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + tr]:last:border-b-0", + className, + )} + {...props} + /> +)); +TableFooter.displayName = "TableFooter"; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableRow.displayName = "TableRow"; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableHead.displayName = "TableHead"; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({ className, ...props }, ref) => ( + +)); +TableCell.displayName = "TableCell"; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)); +TableCaption.displayName = "TableCaption"; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; diff --git a/src/hooks/use-theme.ts b/src/hooks/use-theme.ts new file mode 100644 index 0000000..fe7a46c --- /dev/null +++ b/src/hooks/use-theme.ts @@ -0,0 +1,12 @@ +import { useContext } from "react"; +import { ThemeProviderContext } from "../components/ThemeProvider"; + +export const useTheme = () => { + const context = useContext(ThemeProviderContext); + + if (context === undefined) { + throw new Error("useTheme must be used within a ThemeProvider"); + } + + return context; +}; diff --git a/src/index.css b/src/index.css new file mode 100644 index 0000000..2a5cd60 --- /dev/null +++ b/src/index.css @@ -0,0 +1,234 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; + line-height: 1.5; + font-weight: 400; + + color-scheme: light dark; + color: rgba(255, 255, 255, 0.87); + background-color: #242424; + + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 20 14.3% 4.1%; + --card: 0 0% 100%; + --card-foreground: 20 14.3% 4.1%; + --popover: 0 0% 100%; + --popover-foreground: 20 14.3% 4.1%; + --primary: 24 9.8% 10%; + --primary-foreground: 60 9.1% 97.8%; + --secondary: 60 4.8% 95.9%; + --secondary-foreground: 24 9.8% 10%; + --muted: 60 4.8% 95.9%; + --muted-foreground: 25 5.3% 44.7%; + --accent: 60 4.8% 95.9%; + --accent-foreground: 24 9.8% 10%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 20 5.9% 90%; + --input: 20 5.9% 90%; + --ring: 20 14.3% 4.1%; + --radius: 1rem; + --chart-1: 220 70% 50%; + --chart-2: 340 75% 55%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 160 60% 45%; + --chart-6: 180 50% 50%; + --chart-7: 216 50% 50%; + --chart-8: 252 50% 50%; + --chart-9: 288 50% 50%; + --chart-10: 324 50% 50%; + } + + .dark { + --background: 20 14.3% 4.1%; + --foreground: 60 9.1% 97.8%; + --card: 20 14.3% 4.1%; + --card-foreground: 60 9.1% 97.8%; + --popover: 20 14.3% 4.1%; + --popover-foreground: 60 9.1% 97.8%; + --primary: 60 9.1% 97.8%; + --primary-foreground: 24 9.8% 10%; + --secondary: 12 6.5% 15.1%; + --secondary-foreground: 60 9.1% 97.8%; + --muted: 12 6.5% 15.1%; + --muted-foreground: 24 5.4% 63.9%; + --accent: 12 6.5% 15.1%; + --accent-foreground: 60 9.1% 97.8%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 60 9.1% 97.8%; + --border: 12 6.5% 15.1%; + --input: 12 6.5% 15.1%; + --ring: 24 5.7% 82.9%; + --chart-1: 220 70% 50%; + --chart-2: 340 75% 55%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 160 60% 45%; + --chart-6: 180 50% 50%; + --chart-7: 216 50% 50%; + --chart-8: 252 50% 50%; + --chart-9: 288 50% 50%; + --chart-10: 324 50% 50%; + } +} + +@layer base { + * { + @apply border-border; + } + html { + @apply scroll-smooth; + } + body { + @apply bg-background text-foreground; + /* font-feature-settings: "rlig" 1, "calt" 1; */ + font-synthesis-weight: none; + text-rendering: optimizeLegibility; + } +} + +@layer utilities { + .step { + counter-increment: step; + } + + .step:before { + @apply absolute inline-flex h-9 w-9 items-center justify-center rounded-full border-4 border-background bg-muted text-center -indent-px font-mono text-base font-medium; + @apply ml-[-50px] mt-[-4px]; + content: counter(step); + } +} + +@media (max-width: 640px) { + .container { + @apply px-4; + } +} + +::selection { + @apply bg-stone-300 dark:bg-stone-800; +} + +.hamster-loading-wrapper { + --size: 12px; + height: var(--size); + width: var(--size); + inset: 0; + z-index: 10; +} + +.hamster-loading-wrapper[data-visible="false"] { + transform-origin: center; + animation: hamster-fade-out 0.2s ease forwards; +} + +.hamster-spinner { + position: relative; + top: 50%; + left: 50%; + height: var(--size); + width: var(--size); +} + +.hamster-loading-bar { + --gray11: hsl(0, 0%, 43.5%); + animation: hamster-spin 0.8s linear infinite; + background: var(--gray11); + border-radius: 6px; + height: 13%; + left: -10%; + position: absolute; + top: -3.9%; + width: 30%; +} + +.hamster-loading-bar:nth-child(1) { + animation-delay: -0.8s; + transform: rotate(0deg) translate(120%); +} + +.hamster-loading-bar:nth-child(2) { + animation-delay: -0.7s; + transform: rotate(45deg) translate(120%); +} + +.hamster-loading-bar:nth-child(3) { + animation-delay: -0.6s; + transform: rotate(90deg) translate(120%); +} + +.hamster-loading-bar:nth-child(4) { + animation-delay: -0.5s; + transform: rotate(135deg) translate(120%); +} + +.hamster-loading-bar:nth-child(5) { + animation-delay: -0.4s; + transform: rotate(180deg) translate(120%); +} + +.hamster-loading-bar:nth-child(6) { + animation-delay: -0.3s; + transform: rotate(225deg) translate(120%); +} + +.hamster-loading-bar:nth-child(7) { + animation-delay: -0.2s; + transform: rotate(270deg) translate(120%); +} + +.hamster-loading-bar:nth-child(8) { + animation-delay: -0.1s; + transform: rotate(315deg) translate(120%); +} + +@keyframes hamster-fade-in { + 0% { + opacity: 0; + transform: scale(0.8); + } + 100% { + opacity: 1; + transform: scale(1); + } +} + +@keyframes hamster-fade-out { + 0% { + opacity: 1; + transform: scale(1); + } + 100% { + opacity: 0; + transform: scale(0.8); + } +} + +@keyframes hamster-spin { + 0% { + opacity: 1; + } + 100% { + opacity: 0.15; + } +} + +.scrollbar-hidden { + scrollbar-width: none; /* Firefox */ +} + +.scrollbar-hidden::-webkit-scrollbar { + display: none; /* Chrome, Safari 和 Opera */ +} diff --git a/src/lib/nav-router.ts b/src/lib/nav-router.ts new file mode 100644 index 0000000..c486b69 --- /dev/null +++ b/src/lib/nav-router.ts @@ -0,0 +1,30 @@ +export const navRouter = [ + { + name: "服务器", + path: "/", + }, + { + name: "服务(Dev)", + path: "/service", + }, + { + name: "任务(Dev)", + path: "/task", + }, + { + name: "告警(Dev)", + path: "/alarm", + }, + { + name: "内网穿透(Dev)", + path: "/intranet", + }, + { + name: "用户", + path: "/user", + }, + { + name: "设置(Dev)", + path: "/setting", + }, +]; diff --git a/src/lib/nezha-api.ts b/src/lib/nezha-api.ts new file mode 100644 index 0000000..492c87a --- /dev/null +++ b/src/lib/nezha-api.ts @@ -0,0 +1,80 @@ +export const fetchUsers = async (token: string) => { + const response = await fetch("http://localhost:8008/api/v1/user", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + return data; +}; + +export const createUser = async ( + token: string, + username: string, + password: string, +) => { + const response = await fetch(`http://localhost:8008/api/v1/user`, { + method: "POST", + body: JSON.stringify({ username, password }), + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + return data; +}; + +export const deleteUser = async (token: string, ids: number[]) => { + const response = await fetch( + `http://localhost:8008/api/v1/batch-delete/user`, + { + method: "POST", + body: JSON.stringify(ids), + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + return data; +}; + +export const fetchServers = async (token: string) => { + const response = await fetch("http://localhost:8008/api/v1/server", { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + return data; +}; + +export const deleteServer = async (token: string, ids: number[]) => { + const response = await fetch( + `http://localhost:8008/api/v1/batch-delete/server`, + { + method: "POST", + body: JSON.stringify(ids), + headers: { + Authorization: `Bearer ${token}`, + }, + }, + ); + const data = await response.json(); + if (data.error) { + throw new Error(data.error); + } + return data; +}; diff --git a/src/lib/nezha-model.ts b/src/lib/nezha-model.ts new file mode 100644 index 0000000..9e049c5 --- /dev/null +++ b/src/lib/nezha-model.ts @@ -0,0 +1,79 @@ +/** + * model.Server + */ +export interface ModelServer { + created_at: string; + /** + * DDNS配置 + */ + ddns_profiles: number[]; + deleted_at: string; + /** + * 展示排序,越大越靠前 + */ + display_index: number; + /** + * 启用DDNS + */ + enable_ddns: boolean; + /** + * 对游客隐藏 + */ + hide_for_guest?: boolean; + host?: ModelHost; + id: number; + last_active?: string; + name: string; + /** + * 管理员可见备注 + */ + note: string; + /** + * 公开备注 + */ + public_note: string; + state: ModelHostState; + updated_at: string; + uuid: string; +} + +export interface ModelHost { + arch?: string; + boot_time?: number; + country_code?: string; + cpu?: string[]; + disk_total?: number; + gpu?: string[]; + ip?: string; + mem_total?: number; + platform?: string; + platform_version?: string; + swap_total?: number; + version?: string; + virtualization?: string; +} + +export interface ModelHostState { + cpu?: number; + disk_used?: number; + gpu?: number[]; + load_1?: number; + load_15?: number; + load_5?: number; + mem_used?: number; + net_in_speed?: number; + net_in_transfer?: number; + net_out_speed?: number; + net_out_transfer?: number; + process_count?: number; + swap_used?: number; + tcp_conn_count?: number; + temperatures?: ModelSensorTemperature[]; + udp_conn_count?: number; + uptime?: number; +} + +export interface ModelSensorTemperature { + name?: string; + temperature?: number; +} diff --git a/src/lib/useWebsocket.tsx b/src/lib/useWebsocket.tsx new file mode 100644 index 0000000..0ae0f6d --- /dev/null +++ b/src/lib/useWebsocket.tsx @@ -0,0 +1,89 @@ +import { useState, useEffect, useRef, useCallback } from 'react' + +export interface WebSocketHook { + socket: WebSocket | null + connected: boolean + onlineCount: number + message: string | null + sendMessage: (msg: string) => void +} + +export default function useWebSocket(url: string): WebSocketHook { + const [socket, setSocket] = useState(null) + const [message, setMessage] = useState(null) + const [connected, setConnected] = useState(false) + const [onlineCount, setOnlineCount] = useState(0) + const socketRef = useRef(null) + const reconnectAttempts = useRef(0) + const reconnectTimeout = useRef(null) + const isUnmounted = useRef(false) + + const connect = useCallback(() => { + if (isUnmounted.current) return + + const ws = new WebSocket(url) + setSocket(ws) + socketRef.current = ws + + ws.onopen = () => { + setConnected(true) + reconnectAttempts.current = 0 + } + + ws.onmessage = (event: MessageEvent) => { + setMessage(event.data) + const msgJson = JSON.parse(event.data) + if (msgJson.type === 'live') { + setOnlineCount(msgJson.data.count) + } + } + + ws.onerror = (error) => { + console.error('WebSocket Error:', error) + } + + ws.onclose = () => { + setConnected(false) + if (!isUnmounted.current) { + // Attempt to reconnect + if (reconnectAttempts.current < 5) { + const timeout = Math.pow(2, reconnectAttempts.current) * 1000 // Exponential backoff + reconnectAttempts.current += 1 + reconnectTimeout.current = setTimeout(() => { + connect() + }, timeout) + } else { + console.warn('Max reconnect attempts reached.') + } + } + } + }, [url]) + + useEffect(() => { + connect() + + return () => { + isUnmounted.current = true + if (socketRef.current) { + socketRef.current.close() + } + if (reconnectTimeout.current) { + clearTimeout(reconnectTimeout.current) + } + } + }, [connect]) + + // Function to send messages + const sendMessage = useCallback((msg: string) => { + if (socketRef.current && socketRef.current.readyState === WebSocket.OPEN) { + socketRef.current.send(msg) + } else { + console.warn( + 'WebSocket is not open. Ready state:', + socketRef.current?.readyState + ) + } + }, []) + + return { socket, message, sendMessage, connected, onlineCount } +} diff --git a/src/lib/utils.ts b/src/lib/utils.ts new file mode 100644 index 0000000..a5ef193 --- /dev/null +++ b/src/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)); +} diff --git a/src/lib/websocketProvider.tsx b/src/lib/websocketProvider.tsx new file mode 100644 index 0000000..81ae95f --- /dev/null +++ b/src/lib/websocketProvider.tsx @@ -0,0 +1,26 @@ +import { createContext, useContext, ReactNode } from 'react' +import useWebSocket, { WebSocketHook } from './useWebsocket' + + +interface WebSocketProviderProps { + children: ReactNode +} + +const WebSocketContext = createContext(undefined) + +export const WebSocketProvider = ({ children }: WebSocketProviderProps) => { + const ws = useWebSocket('wss://dev-next.buycoffee.top:4433/api/v1/ws/server') + return ( + {children} + ) +} + +export const useWebSocketContext = (): WebSocketHook => { + const context = useContext(WebSocketContext) + if (!context) { + throw new Error( + 'useWebSocketContext must be used within a WebSocketProvider' + ) + } + return context +} diff --git a/src/main.tsx b/src/main.tsx new file mode 100644 index 0000000..293e182 --- /dev/null +++ b/src/main.tsx @@ -0,0 +1,25 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./App"; +import "./index.css"; +import { ThemeProvider } from "./components/ThemeProvider"; +import { ReactQueryDevtools } from "@tanstack/react-query-devtools"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { Toaster } from "sonner"; +import { WebSocketProvider } from "./lib/websocketProvider"; + +const queryClient = new QueryClient(); + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + + + + + + + + + , +); diff --git a/src/pages/Server.tsx b/src/pages/Server.tsx new file mode 100644 index 0000000..147044f --- /dev/null +++ b/src/pages/Server.tsx @@ -0,0 +1,22 @@ +export default function Servers() { + return ( +
+
+
+

+ 服务器 +

+

+ 你可以在这里查看和管理全部的服务器。 + + 了解更多↗ + +

+
+
+
+ ); +} \ No newline at end of file diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/src/vite-env.d.ts @@ -0,0 +1 @@ +/// diff --git a/tailwind.config.js b/tailwind.config.js new file mode 100644 index 0000000..1fd7518 --- /dev/null +++ b/tailwind.config.js @@ -0,0 +1,59 @@ +module.exports = { + darkMode: ["class"], + content: ["./index.html", "./src/**/*.{js,ts,jsx,tsx}"], + theme: { + extend: { + fontFamily: { + sans: "var(--font-sans)", + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + colors: { + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + chart: { + 1: "hsl(var(--chart-1))", + 2: "hsl(var(--chart-2))", + 3: "hsl(var(--chart-3))", + 4: "hsl(var(--chart-4))", + 5: "hsl(var(--chart-5))", + }, + }, + }, + }, + plugins: [require("tailwindcss-animate")], +}; diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..d1f4998 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + }, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/tsconfig.app.tsbuildinfo b/tsconfig.app.tsbuildinfo new file mode 100644 index 0000000..1847453 --- /dev/null +++ b/tsconfig.app.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./src/app.tsx","./src/main.tsx","./src/vite-env.d.ts","./src/components/footer.tsx","./src/components/header.tsx","./src/components/modetoggle.tsx","./src/components/privateroute.tsx","./src/components/profile.tsx","./src/components/themeprovider.tsx","./src/components/loading/loader.tsx","./src/components/ui/button.tsx","./src/components/ui/dropdown-menu.tsx","./src/hooks/useauth.tsx","./src/lib/nav-router.ts","./src/lib/utils.ts","./src/pages/alarm.tsx","./src/pages/intranet.tsx","./src/pages/login.tsx","./src/pages/server.tsx","./src/pages/service.tsx","./src/pages/setting.tsx","./src/pages/task.tsx","./src/pages/user.tsx"],"version":"5.6.3"} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..fec8c8e --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ], + "compilerOptions": { + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + } +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..9dad701 --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,23 @@ +{ + "compilerOptions": { + "target": "ES2022", + "lib": ["ES2023"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "Bundler", + "allowImportingTsExtensions": true, + "isolatedModules": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/tsconfig.node.tsbuildinfo b/tsconfig.node.tsbuildinfo new file mode 100644 index 0000000..75ea001 --- /dev/null +++ b/tsconfig.node.tsbuildinfo @@ -0,0 +1 @@ +{"root":["./vite.config.ts"],"version":"5.6.3"} \ No newline at end of file diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..4e939e1 --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,13 @@ +import path from "path"; +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react-swc"; + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@": path.resolve(__dirname, "./src"), + }, + }, +});