From 5960ad20bd51476786042d582367ec947ef4af61 Mon Sep 17 00:00:00 2001 From: NriotHrreion Date: Fri, 9 Aug 2024 11:05:22 +0800 Subject: [PATCH] feat: Add dashboard --- app/(pages)/dashboard/page.tsx | 40 +++++++- app/(pages)/settings/page.tsx | 2 +- app/api/fs/disks/route.ts | 11 ++- app/api/os/route.ts | 105 ++++++++++++++++++++ app/providers.tsx | 1 + components/dashboard/battery-widget.tsx | 45 +++++++++ components/dashboard/cpu-widget.tsx | 42 ++++++++ components/dashboard/dashboard-widget.tsx | 24 +++++ components/dashboard/disk-widget.tsx | 89 +++++++++++++++++ components/dashboard/gpu-widget.tsx | 44 +++++++++ components/dashboard/info.tsx | 18 ++++ components/dashboard/memory-widget.tsx | 41 ++++++++ components/dashboard/os-widget.tsx | 29 ++++++ components/explorer/disk-item.tsx | 14 ++- components/explorer/explorer-nav.tsx | 4 +- components/nav.tsx | 15 ++- hooks/useOS.ts | 73 ++++++++++++++ lib/utils.ts | 2 +- next.config.js | 13 ++- package-lock.json | 114 ++++++++++++++++++++++ package.json | 8 ++ tsconfig.json | 2 +- types/index.ts | 10 +- 23 files changed, 718 insertions(+), 28 deletions(-) create mode 100644 app/api/os/route.ts create mode 100644 components/dashboard/battery-widget.tsx create mode 100644 components/dashboard/cpu-widget.tsx create mode 100644 components/dashboard/dashboard-widget.tsx create mode 100644 components/dashboard/disk-widget.tsx create mode 100644 components/dashboard/gpu-widget.tsx create mode 100644 components/dashboard/info.tsx create mode 100644 components/dashboard/memory-widget.tsx create mode 100644 components/dashboard/os-widget.tsx create mode 100644 hooks/useOS.ts diff --git a/app/(pages)/dashboard/page.tsx b/app/(pages)/dashboard/page.tsx index 6139514..7cf89b1 100644 --- a/app/(pages)/dashboard/page.tsx +++ b/app/(pages)/dashboard/page.tsx @@ -1,15 +1,51 @@ +/* eslint-disable padding-line-between-statements */ "use client"; -import { useEffect } from "react"; +import React, { useState, useEffect } from "react"; import { useDetectCookie } from "@/hooks/useDetectCookie"; +// Widgets +import CPUWidget from "@/components/dashboard/cpu-widget"; +import DiskWidget from "@/components/dashboard/disk-widget"; +import MemoryWidget from "@/components/dashboard/memory-widget"; +import GPUWidget from "@/components/dashboard/gpu-widget"; +import BatteryWidget from "@/components/dashboard/battery-widget"; +import OSWidget from "@/components/dashboard/os-widget"; +import { WebSocketContext } from "@/hooks/useOS"; export default function Page() { + const [mounted, setMounted] = useState(false); + const [ws, setWebSocket] = useState(null); + useEffect(() => { + setMounted(true); + document.title = "Ferrum - 仪表盘"; + + const _ws = new WebSocket(`ws://${window.location.host}/api/os`); + setWebSocket(_ws); + + return () => _ws?.close(); }, []); + useEffect(() => { + return () => ws?.close(); + }, [ws]); + useDetectCookie(); - return <>; + if(!mounted) return <>; + + return ( + +
+ + + + + + +
+
+ ); } diff --git a/app/(pages)/settings/page.tsx b/app/(pages)/settings/page.tsx index ed2d552..f7532aa 100644 --- a/app/(pages)/settings/page.tsx +++ b/app/(pages)/settings/page.tsx @@ -46,7 +46,7 @@ export default function Page() { if(!settings) return <>; return ( -
+
({ + used: item.used, + size: item.size, + capacity: (item.used / item.size) * 100, + mount: item.mount + })) }); } catch (err) { // eslint-disable-next-line no-console diff --git a/app/api/os/route.ts b/app/api/os/route.ts new file mode 100644 index 0000000..e9ac107 --- /dev/null +++ b/app/api/os/route.ts @@ -0,0 +1,105 @@ +/* eslint-disable no-console */ +import type { IncomingMessage } from "http"; +import type { WebSocket, WebSocketServer } from "ws"; +import type { OSWebSocketMessage } from "@/hooks/useOS"; + +import os from "os"; + +import si from "systeminformation"; +import { cpuModel, usagePercent as cpuPercent } from "node-system-stats"; +import cookie from "cookie"; + +import { error } from "@/lib/packet"; +import { tokenStorageKey } from "@/lib/global"; +import { validateToken } from "@/lib/token"; + +export function GET() { + return error(400); +} + +export function SOCKET( + client: WebSocket, + req: IncomingMessage, + _server: WebSocketServer, +) { + const token = cookie.parse(req.headers.cookie ?? "")[tokenStorageKey]; + + if(!token) { + client.close(401); + + return; + } + if(!validateToken(token)) { + client.close(403); + + return; + } + + console.log("[Server: /api/os] Socket client connected."); + + const handleRequest = async () => { + const cpu = await si.cpu(); + const cpuTemp = await si.cpuTemperature(); + const mem = await si.mem(); + const memLayout = await si.memLayout(); + const graphics = await si.graphics(); + const battery = await si.battery(); + const disk = await si.fsSize(); + + // if(cpuTemp.main === null) { + // console.warn("CPU temperature info on Windows requires Administrator privilege."); + // } + + client.send(JSON.stringify({ + cpu: { + model: cpuModel, + totalCores: cpu.cores, + usage: (await cpuPercent()).percent, + + /** + * Admin privilege required on Windows + * @see https://systeminformation.io/cpu.html + */ + temperature: cpuTemp.main + }, + memory: { + total: mem.total, + usage: (mem.used / mem.total) * 100, + amount: memLayout.length + }, + gpu: graphics.controllers.map((gpu) => ({ + model: gpu.model, + vendor: gpu.vendor, + memoryTotal: gpu.vram, + memoryUsage: gpu.memoryUsed && gpu.memoryTotal ? ((gpu.memoryUsed / gpu.memoryTotal) * 100) : undefined + })), + battery: { + hasBattery: battery.hasBattery, + isCharging: battery.isCharging, + percent: battery.percent + }, + disk: disk.map((item) => ({ + mount: item.mount, + type: item.type, + size: item.size, + used: item.used + })), + os: { + arch: os.arch(), + platform: os.platform(), + release: os.release(), + version: os.version() + } + } as OSWebSocketMessage)); + }; + + var timer = setInterval(handleRequest, 5000); + + handleRequest(); + + client.on("close", () => { + clearInterval(timer); + + console.log("[Server: /api/os] Socket client disconnected."); + }); +} diff --git a/app/providers.tsx b/app/providers.tsx index 0408e02..cc75102 100644 --- a/app/providers.tsx +++ b/app/providers.tsx @@ -7,6 +7,7 @@ import { ThemeProvider as NextThemesProvider } from "next-themes"; import { ThemeProviderProps } from "next-themes/dist/types"; import { ToastContainer } from "react-toastify"; +// Dialogs import RenameFolderDialog from "@/components/dialogs/rename-folder-dialog"; import RenameFileDialog from "@/components/dialogs/rename-file-dialog"; import RemoveFolderDialog from "@/components/dialogs/remove-folder-dialog"; diff --git a/components/dashboard/battery-widget.tsx b/components/dashboard/battery-widget.tsx new file mode 100644 index 0000000..24f28b1 --- /dev/null +++ b/components/dashboard/battery-widget.tsx @@ -0,0 +1,45 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; +import { Progress } from "@nextui-org/progress"; + +import DashboardWidget from "./dashboard-widget"; + +import { type BatteryInfo, useOS } from "@/hooks/useOS"; + +const BatteryWidget: React.FC = (props) => { + const [batteryInfo, setBatteryInfo] = useState(null); + + useOS(({ battery }) => { + setBatteryInfo(battery); + }); + + return ( + + { + batteryInfo?.hasBattery + ? ( + <> +

{batteryInfo?.isCharging ? "正在充电" : ""}

+ +
+ {batteryInfo?.percent ? `${batteryInfo?.percent}%` : "--%"} + + +
+ + ) + :

未发现电池

+ } +
+ ); +} + +export default BatteryWidget; diff --git a/components/dashboard/cpu-widget.tsx b/components/dashboard/cpu-widget.tsx new file mode 100644 index 0000000..673f080 --- /dev/null +++ b/components/dashboard/cpu-widget.tsx @@ -0,0 +1,42 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; +import { Progress } from "@nextui-org/progress"; + +import DashboardWidget from "./dashboard-widget"; + +import { type CPUInfo, useOS } from "@/hooks/useOS"; + +const CPUWidget: React.FC = (props) => { + const [cpuInfo, setCPUInfo] = useState(null); + + useOS(({ cpu }) => { + setCPUInfo(cpu); + }); + + return ( + +

{cpuInfo?.model} {cpuInfo?.totalCores ? `(${cpuInfo.totalCores}核)` : ""}

+ +
+
+ + 温度: + {cpuInfo?.temperature ? `${cpuInfo?.temperature.toFixed(2)}°C` : "--°C"} + + {cpuInfo?.usage ? `${cpuInfo?.usage}%` : "--%"} +
+ + +
+
+ ); +} + +export default CPUWidget; diff --git a/components/dashboard/dashboard-widget.tsx b/components/dashboard/dashboard-widget.tsx new file mode 100644 index 0000000..2084cec --- /dev/null +++ b/components/dashboard/dashboard-widget.tsx @@ -0,0 +1,24 @@ +import type { PropsWithCN } from "@/types"; + +import React, { type PropsWithChildren } from "react"; +import { Card } from "@nextui-org/card"; +import { cn } from "@nextui-org/theme"; + +interface DashboardWidgetProps extends PropsWithCN, PropsWithChildren { + name: string; + insideClassName?: string +} + +const DashboardWidget: React.FC = (props) => { + return ( + + {props.name} + +
+ {props.children} +
+
+ ); +} + +export default DashboardWidget; diff --git a/components/dashboard/disk-widget.tsx b/components/dashboard/disk-widget.tsx new file mode 100644 index 0000000..fe1453e --- /dev/null +++ b/components/dashboard/disk-widget.tsx @@ -0,0 +1,89 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; +import { useRouter } from "next/navigation"; +import { cn } from "@nextui-org/theme"; +import { HardDrive } from "lucide-react"; +import { Progress } from "@nextui-org/progress"; +import { Chip } from "@nextui-org/chip"; + +import DashboardWidget from "./dashboard-widget"; + +import { type DiskInfo, useOS } from "@/hooks/useOS"; +import { scrollbarStyle } from "@/lib/style"; +import { formatSize } from "@/lib/utils"; +import { useExplorer } from "@/hooks/useExplorer"; + +const DiskWidget: React.FC = (props) => { + const [diskInfo, setDiskInfo] = useState(null); + const explorer = useExplorer(); + const router = useRouter(); + + useOS(({ disk }) => { + setDiskInfo(disk); + }); + + return ( + + { + diskInfo?.map((disk, index) => { + const capacity = (disk.used / disk.size) * 100; + const size = formatSize(disk.size); + const used = formatSize(disk.used); + + const handleClick = () => { + explorer.setDisk(disk.mount); + explorer.backToRoot(); + router.push("/explorer"); + }; + + return ( +
handleClick()} + onKeyDown={({ key }) => { + if(key === "Enter") { + handleClick(); + } + }} + role="button" + tabIndex={-1} + key={index}> +
+ + +
+ + {disk.mount} + + +
+ {used +" / "+ size} + {disk.type} +
+
+
+ +
+ +
+
+ ); + }) + } +
+ ); +} + +export default DiskWidget; diff --git a/components/dashboard/gpu-widget.tsx b/components/dashboard/gpu-widget.tsx new file mode 100644 index 0000000..29dcaaa --- /dev/null +++ b/components/dashboard/gpu-widget.tsx @@ -0,0 +1,44 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; +import { Progress } from "@nextui-org/progress"; +import { cn } from "@nextui-org/theme"; + +import DashboardWidget from "./dashboard-widget"; +import Info from "./info"; + +import { type GPUInfo, useOS } from "@/hooks/useOS"; +import { scrollbarStyle } from "@/lib/style"; + +const GPUWidget: React.FC = (props) => { + const [gpuInfo, setGPUInfo] = useState(null); + + useOS(({ gpu }) => { + setGPUInfo(gpu); + }); + + return ( + + { + gpuInfo?.map((gpu, index) => ( +
+ + {gpu.memoryTotal && } + {gpu.memoryUsage && ( + + )} +
+ )) + } +
+ ); +} + +export default GPUWidget; diff --git a/components/dashboard/info.tsx b/components/dashboard/info.tsx new file mode 100644 index 0000000..04d5a3c --- /dev/null +++ b/components/dashboard/info.tsx @@ -0,0 +1,18 @@ +import type { PropsWithCN } from "@/types"; + +import React from "react"; + +interface InfoProps extends PropsWithCN { + name: string + content: string +} + +const Info: React.FC = (props) => { + return ( +

+ {props.name}:{props.content} +

+ ); +} + +export default Info; diff --git a/components/dashboard/memory-widget.tsx b/components/dashboard/memory-widget.tsx new file mode 100644 index 0000000..505f9c9 --- /dev/null +++ b/components/dashboard/memory-widget.tsx @@ -0,0 +1,41 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; +import { Progress } from "@nextui-org/progress"; + +import DashboardWidget from "./dashboard-widget"; +import Info from "./info"; + +import { type MemoryInfo, useOS } from "@/hooks/useOS"; +import { formatSize } from "@/lib/utils"; + +const MemoryWidget: React.FC = (props) => { + const [memoryInfo, setMemoryInfo] = useState(null); + + useOS(({ memory }) => { + setMemoryInfo(memory); + }); + + return ( + + + + +
+
+ {memoryInfo?.usage ? `${memoryInfo?.usage.toFixed(2)}%` : "--%"} +
+ + +
+
+ ); +} + +export default MemoryWidget; diff --git a/components/dashboard/os-widget.tsx b/components/dashboard/os-widget.tsx new file mode 100644 index 0000000..0ca72e4 --- /dev/null +++ b/components/dashboard/os-widget.tsx @@ -0,0 +1,29 @@ +import type { PropsWithCN } from "@/types"; + +import React, { useState } from "react"; + +import DashboardWidget from "./dashboard-widget"; +import Info from "./info"; + +import { type OSInfo, useOS } from "@/hooks/useOS"; + +const OSWidget: React.FC = (props) => { + const [osInfo, setOSInfo] = useState(null); + + useOS(({ os }) => { + setOSInfo(os); + }); + + return ( + + + + + + ); +} + +export default OSWidget; diff --git a/components/explorer/disk-item.tsx b/components/explorer/disk-item.tsx index b451687..6e28e87 100644 --- a/components/explorer/disk-item.tsx +++ b/components/explorer/disk-item.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React from "react"; import { Progress } from "@nextui-org/progress"; import { Drive } from "@/types"; @@ -9,27 +9,25 @@ interface DiskItemProps extends Drive { } const DiskItem: React.FC = (props) => { - const capacity = useMemo(() => parseFloat(props._capacity), [props._capacity]); - return (
- {props._mounted} + {props.mount} - {formatSize(props._used, 1)} / {formatSize(props._blocks, 1)} + {formatSize(props.used, 1)} / {formatSize(props.size, 1)}
+ aria-label={"磁盘空间占用:"+ props.capacity.toFixed(1) +"%"}/>
); } diff --git a/components/explorer/explorer-nav.tsx b/components/explorer/explorer-nav.tsx index b04c374..a1dcce1 100644 --- a/components/explorer/explorer-nav.tsx +++ b/components/explorer/explorer-nav.tsx @@ -153,10 +153,10 @@ const ExplorerNav: React.FC = () => { { ferrum.disks.map((disk) => ( } - aria-label={disk._mounted}> + aria-label={disk.mount}> )) diff --git a/components/nav.tsx b/components/nav.tsx index d27bb36..fd0d7e9 100644 --- a/components/nav.tsx +++ b/components/nav.tsx @@ -1,6 +1,6 @@ "use client"; -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import { Navbar, NavbarBrand, @@ -18,8 +18,9 @@ type NavPage = "dashboard" | "explorer" | "settings"; const Nav: React.FC = () => { const pathname = usePathname(); - const [page, setPage] = useState(() => { - switch(pathname) { + + const handlePathnameChange = (changed: string) => { + switch(changed) { case "/dashboard": return "dashboard"; case "/explorer": @@ -30,8 +31,16 @@ const Nav: React.FC = () => { default: return null; } + }; + + const [page, setPage] = useState(() => { + return handlePathnameChange(pathname); }); + useEffect(() => { + setPage(handlePathnameChange(pathname)); + }, [pathname]); + return ( ({ ws: null }); + +export interface CPUInfo { + model: string + totalCores: number + usage: number + temperature: number +} + +export interface MemoryInfo { + total: number + usage: number + amount: number +} + +export interface GPUInfo { + vendor: string + model: string + memoryTotal?: number + memoryUsage?: number +} + +export interface BatteryInfo { + hasBattery: boolean + isCharging: boolean + percent: number +} + +export interface DiskInfo { + mount: string + type: string + size: number + used: number +} + +export interface OSInfo { + arch: string + platform: NodeJS.Platform + release: string + version: string +} + +export interface OSWebSocketMessage { + cpu: CPUInfo + memory: MemoryInfo + gpu: GPUInfo[] + battery: BatteryInfo + disk: DiskInfo[] + os: OSInfo +} + +/** Used by dashboard widgets to fetch os info from the ws interface (`/api/os`). */ +export function useOS(listener: (msg: OSWebSocketMessage) => void) { + const { ws } = useContext(WebSocketContext); + + useEffect(() => { + const controller = new AbortController(); + + ws?.addEventListener("message", (e) => { + const msg = JSON.parse(e.data) as OSWebSocketMessage; + + listener(msg); + }, { signal: controller.signal }); + + return () => controller.abort(); + }, [ws]); +} diff --git a/lib/utils.ts b/lib/utils.ts index a40dc69..395294f 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -49,7 +49,7 @@ export function formatSize(bytes: number, fixed: number = 2): string { size = bytesSizeTransform(bytes, BytesType.B, BytesType.TB, fixed); } - return size.value + getBytesType(size.type); + return size.value +" "+ getBytesType(size.type); } export function getExtname(fileName: string): string { diff --git a/next.config.js b/next.config.js index 767719f..ab30079 100644 --- a/next.config.js +++ b/next.config.js @@ -1,4 +1,13 @@ -/** @type {import('next').NextConfig} */ -const nextConfig = {} +const { webpack } = require("next/dist/compiled/webpack/webpack"); + +/** @type {import("next").NextConfig} */ +const nextConfig = { + webpack: (config) => { + /** @see https://github.com/sebhildebrandt/systeminformation/issues/230#issuecomment-610371026 */ + config.plugins.push(new webpack.IgnorePlugin({ resourceRegExp: /osx-temperature-sensor$/ })); + + return config; + } +} module.exports = nextConfig diff --git a/package-lock.json b/package-lock.json index ed9bf4d..d31395f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,6 +13,7 @@ "@nextui-org/button": "2.0.34", "@nextui-org/card": "^2.0.31", "@nextui-org/checkbox": "^2.1.4", + "@nextui-org/chip": "^2.0.32", "@nextui-org/code": "2.0.29", "@nextui-org/divider": "^2.0.28", "@nextui-org/dropdown": "^2.1.27", @@ -37,6 +38,7 @@ "@xiaohuohumax/lrc-parser": "^1.0.1", "ace-builds": "^1.35.4", "axios": "^1.7.2", + "cookie": "^0.6.0", "filepond-plugin-image-preview": "^4.6.12", "framer-motion": "~11.1.1", "intl-messageformat": "^10.5.0", @@ -47,7 +49,9 @@ "music-metadata": "^10.0.0", "next": "14.2.4", "next-themes": "^0.2.1", + "next-ws": "^1.1.1", "node-disk-info": "^1.3.0", + "node-system-stats": "^1.3.0", "pdfjs-dist": "^4.5.136", "preps": "^0.1.1", "react": "18.3.1", @@ -58,14 +62,17 @@ "react-pdf": "^9.1.0", "react-svg": "^16.1.34", "react-toastify": "^10.0.5", + "systeminformation": "^5.23.4", "tailwindcss": "3.4.3", "use-context-menu": "^0.5.0", "uuid": "^10.0.0", "validator": "^13.12.0", + "ws": "^8.18.0", "zod": "^3.23.8", "zustand": "^4.5.4" }, "devDependencies": { + "@types/cookie": "^0.6.0", "@types/js-cookie": "^3.0.6", "@types/md5": "^2.3.5", "@types/node": "20.5.7", @@ -73,6 +80,7 @@ "@types/react-dom": "18.3.0", "@types/uuid": "^10.0.0", "@types/validator": "^13.12.0", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", "autoprefixer": "10.4.19", @@ -807,6 +815,26 @@ "react-dom": ">=18" } }, + "node_modules/@nextui-org/chip": { + "version": "2.0.32", + "resolved": "https://registry.npmmirror.com/@nextui-org/chip/-/chip-2.0.32.tgz", + "integrity": "sha512-fGqXamG7xs+DvKPra+rJEkIAjaQwPi8FSvsJ4P4LWzQ3U+HjymEI07BW8xQmaLceHInbTLTfdbTjAYdGNzAdOQ==", + "dependencies": { + "@nextui-org/react-utils": "2.0.16", + "@nextui-org/shared-icons": "2.0.9", + "@nextui-org/shared-utils": "2.0.7", + "@react-aria/focus": "3.17.1", + "@react-aria/interactions": "3.21.3", + "@react-aria/utils": "3.24.1", + "@react-types/checkbox": "3.8.1" + }, + "peerDependencies": { + "@nextui-org/system": ">=2.0.0", + "@nextui-org/theme": ">=2.1.0", + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/@nextui-org/code": { "version": "2.0.29", "resolved": "https://registry.npmmirror.com/@nextui-org/code/-/code-2.0.29.tgz", @@ -3694,6 +3722,12 @@ "resolved": "https://registry.npmmirror.com/@tokenizer/token/-/token-0.3.0.tgz", "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "dev": true + }, "node_modules/@types/js-cookie": { "version": "3.0.6", "resolved": "https://registry.npmmirror.com/@types/js-cookie/-/js-cookie-3.0.6.tgz", @@ -3766,6 +3800,15 @@ "integrity": "sha512-nH45Lk7oPIJ1RVOF6JgFI6Dy0QpHEzq4QecZhvguxYPDwT8c93prCMqAtiIttm39voZ+DDR+qkNnMpJmMBRqag==", "dev": true }, + "node_modules/@types/ws": { + "version": "8.5.12", + "resolved": "https://registry.npmmirror.com/@types/ws/-/ws-8.5.12.tgz", + "integrity": "sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "7.2.0", "resolved": "https://registry.npmmirror.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.2.0.tgz", @@ -4674,6 +4717,14 @@ "node": ">= 0.6" } }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmmirror.com/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/cross-spawn/-/cross-spawn-7.0.3.tgz", @@ -7596,6 +7647,16 @@ "react-dom": "*" } }, + "node_modules/next-ws": { + "version": "1.1.1", + "resolved": "https://registry.npmmirror.com/next-ws/-/next-ws-1.1.1.tgz", + "integrity": "sha512-J/wNgcd1lXIkrFbRNP60lNVeAVieIDlxahkQ5jpfX3QJhuLPYLBh/CFvX82nkLUBlXANQbTqg9F9yU4XxjmUIg==", + "peerDependencies": { + "next": ">=13.1.1", + "react": "*", + "ws": "*" + } + }, "node_modules/next/node_modules/@swc/helpers": { "version": "0.5.5", "resolved": "https://registry.npmmirror.com/@swc/helpers/-/helpers-0.5.5.tgz", @@ -7669,6 +7730,14 @@ "integrity": "sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g==", "dev": true }, + "node_modules/node-system-stats": { + "version": "1.3.0", + "resolved": "https://registry.npmmirror.com/node-system-stats/-/node-system-stats-1.3.0.tgz", + "integrity": "sha512-T5ve50fR+yTh8H3SlQ+k6nJUea5wXnf5jF0OXS5Z97+QWp1gQnQ23seirKftu8+ePzIMDfklmugXJ9qgCu0WLA==", + "engines": { + "node": ">=16.9.0" + } + }, "node_modules/nopt": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/nopt/-/nopt-5.0.0.tgz", @@ -9260,6 +9329,31 @@ "url": "https://opencollective.com/unts" } }, + "node_modules/systeminformation": { + "version": "5.23.4", + "resolved": "https://registry.npmmirror.com/systeminformation/-/systeminformation-5.23.4.tgz", + "integrity": "sha512-mD2R9xnOzKOOmIVtxekosf/ghOE/DGLqAPmsEgQMWJK0pMKxBtX19riz1Ss0tN4omcfS2FQ2RDJ4lkxgADxIPw==", + "os": [ + "darwin", + "linux", + "win32", + "freebsd", + "openbsd", + "netbsd", + "sunos", + "android" + ], + "bin": { + "systeminformation": "lib/cli.js" + }, + "engines": { + "node": ">=8.0.0" + }, + "funding": { + "type": "Buy me a coffee", + "url": "https://www.buymeacoffee.com/systeminfo" + } + }, "node_modules/tailwind-merge": { "version": "1.14.0", "resolved": "https://registry.npmmirror.com/tailwind-merge/-/tailwind-merge-1.14.0.tgz", @@ -10007,6 +10101,26 @@ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", "devOptional": true }, + "node_modules/ws": { + "version": "8.18.0", + "resolved": "https://registry.npmmirror.com/ws/-/ws-8.18.0.tgz", + "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "4.0.0", "resolved": "https://registry.npmmirror.com/yallist/-/yallist-4.0.0.tgz", diff --git a/package.json b/package.json index 008cdd9..e0c8bda 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@nextui-org/button": "2.0.34", "@nextui-org/card": "^2.0.31", "@nextui-org/checkbox": "^2.1.4", + "@nextui-org/chip": "^2.0.32", "@nextui-org/code": "2.0.29", "@nextui-org/divider": "^2.0.28", "@nextui-org/dropdown": "^2.1.27", @@ -38,6 +39,7 @@ "@xiaohuohumax/lrc-parser": "^1.0.1", "ace-builds": "^1.35.4", "axios": "^1.7.2", + "cookie": "^0.6.0", "filepond-plugin-image-preview": "^4.6.12", "framer-motion": "~11.1.1", "intl-messageformat": "^10.5.0", @@ -48,7 +50,9 @@ "music-metadata": "^10.0.0", "next": "14.2.4", "next-themes": "^0.2.1", + "next-ws": "^1.1.1", "node-disk-info": "^1.3.0", + "node-system-stats": "^1.3.0", "pdfjs-dist": "^4.5.136", "preps": "^0.1.1", "react": "18.3.1", @@ -59,14 +63,17 @@ "react-pdf": "^9.1.0", "react-svg": "^16.1.34", "react-toastify": "^10.0.5", + "systeminformation": "^5.23.4", "tailwindcss": "3.4.3", "use-context-menu": "^0.5.0", "uuid": "^10.0.0", "validator": "^13.12.0", + "ws": "^8.18.0", "zod": "^3.23.8", "zustand": "^4.5.4" }, "devDependencies": { + "@types/cookie": "^0.6.0", "@types/js-cookie": "^3.0.6", "@types/md5": "^2.3.5", "@types/node": "20.5.7", @@ -74,6 +81,7 @@ "@types/react-dom": "18.3.0", "@types/uuid": "^10.0.0", "@types/validator": "^13.12.0", + "@types/ws": "^8.5.12", "@typescript-eslint/eslint-plugin": "7.2.0", "@typescript-eslint/parser": "7.2.0", "autoprefixer": "10.4.19", diff --git a/tsconfig.json b/tsconfig.json index 83fba45..59cc25d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -8,7 +8,7 @@ "forceConsistentCasingInFileNames": true, "noEmit": true, "esModuleInterop": true, - "module": "esnext", + "module": "ESNext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, diff --git a/types/index.ts b/types/index.ts index 81df738..800abde 100644 --- a/types/index.ts +++ b/types/index.ts @@ -22,12 +22,10 @@ export enum BytesType { } export interface Drive { - readonly _filesystem: string - readonly _blocks: number - readonly _used: number - readonly _available: number - readonly _capacity: string - readonly _mounted: string + used: number + size: number + capacity: number + mount: string } export type SystemPlatform = "aix" | "darwin" | "freebsd" | "linux" | "openbsd" | "sunos" | "win32";