From b1d8b1a82582e7def9a5526cba3c760b4579f129 Mon Sep 17 00:00:00 2001 From: samanhappy Date: Wed, 9 Apr 2025 11:29:35 +0800 Subject: [PATCH] refactor frontend code --- .gitignore | 2 +- Dockerfile | 2 +- {public => frontend}/favicon.ico | Bin frontend/index.html | 13 + frontend/postcss.config.js | 6 + frontend/src/App.tsx | 156 +++ frontend/src/components/AddServerForm.tsx | 55 ++ frontend/src/components/EditServerForm.tsx | 44 + frontend/src/components/ServerCard.tsx | 83 ++ frontend/src/components/ServerForm.tsx | 269 +++++ frontend/src/components/icons/LucideIcons.tsx | 10 + frontend/src/components/ui/Badge.tsx | 23 + frontend/src/components/ui/DeleteDialog.tsx | 37 + frontend/src/components/ui/ToolCard.tsx | 38 + frontend/src/index.css | 20 + frontend/src/main.tsx | 10 + frontend/src/types/index.ts | 55 ++ frontend/tsconfig.json | 31 + frontend/tsconfig.node.json | 10 + frontend/vite.config.ts | 22 + package.json | 17 +- pnpm-lock.yaml | 925 +++++++++++++++++- public/components/DeleteDialog.jsx | 36 - public/components/LucideIcons.jsx | 44 - public/index.html | 20 - public/js/app.js | 677 ------------- src/middlewares/index.ts | 4 +- 27 files changed, 1822 insertions(+), 787 deletions(-) rename {public => frontend}/favicon.ico (100%) create mode 100644 frontend/index.html create mode 100644 frontend/postcss.config.js create mode 100644 frontend/src/App.tsx create mode 100644 frontend/src/components/AddServerForm.tsx create mode 100644 frontend/src/components/EditServerForm.tsx create mode 100644 frontend/src/components/ServerCard.tsx create mode 100644 frontend/src/components/ServerForm.tsx create mode 100644 frontend/src/components/icons/LucideIcons.tsx create mode 100644 frontend/src/components/ui/Badge.tsx create mode 100644 frontend/src/components/ui/DeleteDialog.tsx create mode 100644 frontend/src/components/ui/ToolCard.tsx create mode 100644 frontend/src/index.css create mode 100644 frontend/src/main.tsx create mode 100644 frontend/src/types/index.ts create mode 100644 frontend/tsconfig.json create mode 100644 frontend/tsconfig.node.json create mode 100644 frontend/vite.config.ts delete mode 100644 public/components/DeleteDialog.jsx delete mode 100644 public/components/LucideIcons.jsx delete mode 100644 public/index.html delete mode 100644 public/js/app.js diff --git a/.gitignore b/.gitignore index 36a2bbd..1bb3bc7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,7 +4,7 @@ .pnp.js # production -/dist +dist # env files .env diff --git a/Dockerfile b/Dockerfile index 1bf5a94..97947e0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,7 +20,7 @@ RUN pnpm install @amap/amap-maps-mcp-server @playwright/mcp@latest tavily-mcp@la COPY . . -RUN pnpm build +RUN pnpm frontend:build && pnpm build COPY entrypoint.sh /usr/local/bin/entrypoint.sh RUN chmod +x /usr/local/bin/entrypoint.sh diff --git a/public/favicon.ico b/frontend/favicon.ico similarity index 100% rename from public/favicon.ico rename to frontend/favicon.ico diff --git a/frontend/index.html b/frontend/index.html new file mode 100644 index 0000000..8449b96 --- /dev/null +++ b/frontend/index.html @@ -0,0 +1,13 @@ + + + + + + MCP Hub Dashboard + + + +
+ + + \ No newline at end of file diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js new file mode 100644 index 0000000..51a6e4e --- /dev/null +++ b/frontend/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx new file mode 100644 index 0000000..680b66a --- /dev/null +++ b/frontend/src/App.tsx @@ -0,0 +1,156 @@ +import { useState, useEffect } from 'react' +import { Server, ApiResponse } from './types' +import ServerCard from './components/ServerCard' +import AddServerForm from './components/AddServerForm' +import EditServerForm from './components/EditServerForm' + +function App() { + const [servers, setServers] = useState([]) + const [error, setError] = useState(null) + const [refreshKey, setRefreshKey] = useState(0) + const [editingServer, setEditingServer] = useState(null) + + useEffect(() => { + const fetchServers = async () => { + try { + const response = await fetch('/api/servers') + const data = await response.json() + + // 处理API响应中的包装对象,提取data字段 + if (data && data.success && Array.isArray(data.data)) { + setServers(data.data) + } else if (data && Array.isArray(data)) { + // 兼容性处理,如果API直接返回数组 + setServers(data) + } else { + // 如果数据格式不符合预期,设置为空数组 + console.error('Invalid server data format:', data) + setServers([]) + } + } catch (err) { + setError(err instanceof Error ? err.message : String(err)) + } + } + + fetchServers() + + // Poll for updates every 5 seconds + const interval = setInterval(fetchServers, 5000) + return () => clearInterval(interval) + }, [refreshKey]) + + const handleServerAdd = () => { + setRefreshKey(prevKey => prevKey + 1) + } + + const handleServerEdit = (server: Server) => { + // Fetch settings to get the full server config before editing + fetch(`/api/settings`) + .then(response => response.json()) + .then((settingsData: ApiResponse<{ mcpServers: Record }>) => { + if ( + settingsData && + settingsData.success && + settingsData.data && + settingsData.data.mcpServers && + settingsData.data.mcpServers[server.name] + ) { + const serverConfig = settingsData.data.mcpServers[server.name] + const fullServerData = { + name: server.name, + status: server.status, + tools: server.tools || [], + config: serverConfig, + } + + console.log('Editing server with config:', fullServerData) + setEditingServer(fullServerData) + } else { + console.error('Failed to get server config from settings:', settingsData) + setError(`Could not find configuration data for ${server.name}`) + } + }) + .catch(err => { + console.error('Error fetching server settings:', err) + setError(err instanceof Error ? err.message : String(err)) + }) + } + + const handleEditComplete = () => { + setEditingServer(null) + setRefreshKey(prevKey => prevKey + 1) + } + + const handleServerRemove = async (serverName: string) => { + try { + const response = await fetch(`/api/servers/${serverName}`, { + method: 'DELETE', + }) + const result = await response.json() + + if (!response.ok) { + setError(result.message || `Failed to delete server ${serverName}`) + return + } + + setRefreshKey(prevKey => prevKey + 1) + } catch (err) { + setError('Error: ' + (err instanceof Error ? err.message : String(err))) + } + } + + if (error) { + return ( +
+
+
+

Error

+

{error}

+ +
+
+
+ ) + } + + return ( +
+
+
+

MCP Hub Dashboard

+ +
+ {servers.length === 0 ? ( +
+

No MCP servers available

+
+ ) : ( +
+ {servers.map((server, index) => ( + + ))} +
+ )} + {editingServer && ( + setEditingServer(null)} + /> + )} +
+
+ ) +} + +export default App \ No newline at end of file diff --git a/frontend/src/components/AddServerForm.tsx b/frontend/src/components/AddServerForm.tsx new file mode 100644 index 0000000..1499cce --- /dev/null +++ b/frontend/src/components/AddServerForm.tsx @@ -0,0 +1,55 @@ +import { useState } from 'react' +import ServerForm from './ServerForm' + +interface AddServerFormProps { + onAdd: () => void +} + +const AddServerForm = ({ onAdd }: AddServerFormProps) => { + const [modalVisible, setModalVisible] = useState(false) + + const toggleModal = () => { + setModalVisible(!modalVisible) + } + + const handleSubmit = async (payload: any) => { + try { + const response = await fetch('/api/servers', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }) + + const result = await response.json() + + if (!response.ok) { + alert(result.message || 'Failed to add server') + return + } + + setModalVisible(false) + onAdd() + } catch (err) { + alert(`Error: ${err instanceof Error ? err.message : String(err)}`) + } + } + + return ( +
+ + + {modalVisible && ( +
+ +
+ )} +
+ ) +} + +export default AddServerForm \ No newline at end of file diff --git a/frontend/src/components/EditServerForm.tsx b/frontend/src/components/EditServerForm.tsx new file mode 100644 index 0000000..5659ed4 --- /dev/null +++ b/frontend/src/components/EditServerForm.tsx @@ -0,0 +1,44 @@ +import { Server } from '@/types' +import ServerForm from './ServerForm' + +interface EditServerFormProps { + server: Server + onEdit: () => void + onCancel: () => void +} + +const EditServerForm = ({ server, onEdit, onCancel }: EditServerFormProps) => { + const handleSubmit = async (payload: any) => { + try { + const response = await fetch(`/api/servers/${server.name}`, { + method: 'PUT', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(payload), + }) + + const result = await response.json() + + if (!response.ok) { + alert(result.message || 'Failed to update server') + return + } + + onEdit() + } catch (err) { + alert(`Error: ${err instanceof Error ? err.message : String(err)}`) + } + } + + return ( +
+ +
+ ) +} + +export default EditServerForm \ No newline at end of file diff --git a/frontend/src/components/ServerCard.tsx b/frontend/src/components/ServerCard.tsx new file mode 100644 index 0000000..e8b707e --- /dev/null +++ b/frontend/src/components/ServerCard.tsx @@ -0,0 +1,83 @@ +import { useState } from 'react' +import { Server } from '@/types' +import { ChevronDown, ChevronRight } from '@/components/icons/LucideIcons' +import Badge from '@/components/ui/Badge' +import ToolCard from '@/components/ui/ToolCard' +import DeleteDialog from '@/components/ui/DeleteDialog' + +interface ServerCardProps { + server: Server + onRemove: (serverName: string) => void + onEdit: (server: Server) => void +} + +const ServerCard = ({ server, onRemove, onEdit }: ServerCardProps) => { + const [isExpanded, setIsExpanded] = useState(false) + const [showDeleteDialog, setShowDeleteDialog] = useState(false) + + const handleRemove = (e: React.MouseEvent) => { + e.stopPropagation() + setShowDeleteDialog(true) + } + + const handleEdit = (e: React.MouseEvent) => { + e.stopPropagation() + onEdit(server) + } + + const handleConfirmDelete = () => { + onRemove(server.name) + setShowDeleteDialog(false) + } + + return ( +
+
setIsExpanded(!isExpanded)} + > +
+

{server.name}

+ +
+
+ + + +
+
+ + setShowDeleteDialog(false)} + onConfirm={handleConfirmDelete} + serverName={server.name} + /> + + {isExpanded && server.tools && ( +
+

Available Tools

+
+ {server.tools.map((tool, index) => ( + + ))} +
+
+ )} +
+ ) +} + +export default ServerCard \ No newline at end of file diff --git a/frontend/src/components/ServerForm.tsx b/frontend/src/components/ServerForm.tsx new file mode 100644 index 0000000..a71bd72 --- /dev/null +++ b/frontend/src/components/ServerForm.tsx @@ -0,0 +1,269 @@ +import { useState } from 'react' +import { Server, EnvVar, ServerFormData } from '@/types' + +interface ServerFormProps { + onSubmit: (payload: any) => void + onCancel: () => void + initialData?: Server | null + modalTitle: string +} + +const ServerForm = ({ onSubmit, onCancel, initialData = null, modalTitle }: ServerFormProps) => { + const [serverType, setServerType] = useState<'sse' | 'stdio'>( + initialData && initialData.config && initialData.config.url ? 'sse' : 'stdio', + ) + + const [formData, setFormData] = useState({ + name: (initialData && initialData.name) || '', + url: (initialData && initialData.config && initialData.config.url) || '', + command: (initialData && initialData.config && initialData.config.command) || '', + arguments: + initialData && initialData.config && initialData.config.args + ? Array.isArray(initialData.config.args) + ? initialData.config.args.join(' ') + : String(initialData.config.args) + : '', + args: (initialData && initialData.config && initialData.config.args) || [], + }) + + const [envVars, setEnvVars] = useState( + initialData && initialData.config && initialData.config.env + ? Object.entries(initialData.config.env).map(([key, value]) => ({ key, value })) + : [], + ) + + const [error, setError] = useState(null) + const isEdit = !!initialData + + const handleInputChange = (e: React.ChangeEvent) => { + const { name, value } = e.target + setFormData({ ...formData, [name]: value }) + } + + // Transform space-separated arguments string into array + const handleArgsChange = (value: string) => { + let args = value.split(' ').filter((arg) => arg.trim() !== '') + setFormData({ ...formData, arguments: value, args }) + } + + const handleEnvVarChange = (index: number, field: 'key' | 'value', value: string) => { + const newEnvVars = [...envVars] + newEnvVars[index][field] = value + setEnvVars(newEnvVars) + } + + const addEnvVar = () => { + setEnvVars([...envVars, { key: '', value: '' }]) + } + + const removeEnvVar = (index: number) => { + const newEnvVars = [...envVars] + newEnvVars.splice(index, 1) + setEnvVars(newEnvVars) + } + + // Submit handler for server configuration + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault() + setError(null) + + try { + const env: Record = {} + envVars.forEach(({ key, value }) => { + if (key.trim()) { + env[key.trim()] = value + } + }) + + const payload = { + name: formData.name, + config: + serverType === 'sse' + ? { url: formData.url } + : { + command: formData.command, + args: formData.args, + env: Object.keys(env).length > 0 ? env : undefined, + }, + } + + onSubmit(payload) + } catch (err) { + setError(`Error: ${err instanceof Error ? err.message : String(err)}`) + } + } + + return ( +
+
+

{modalTitle}

+ +
+ + {error &&
{error}
} + +
+
+ + +
+ +
+ +
+
+ setServerType('stdio')} + className="mr-1" + /> + +
+
+ setServerType('sse')} + className="mr-1" + /> + +
+
+
+ + {serverType === 'sse' ? ( +
+ + +
+ ) : ( + <> +
+ + +
+
+ + handleArgsChange(e.target.value)} + className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" + placeholder="e.g.: -y time-mcp" + required={serverType === 'stdio'} + /> +
+ +
+
+ + +
+ {envVars.map((envVar, index) => ( +
+
+ handleEnvVarChange(index, 'key', e.target.value)} + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + placeholder="key" + /> + : + handleEnvVarChange(index, 'value', e.target.value)} + className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" + placeholder="value" + /> +
+ +
+ ))} +
+ + )} + +
+ + +
+
+
+ ) +} + +export default ServerForm \ No newline at end of file diff --git a/frontend/src/components/icons/LucideIcons.tsx b/frontend/src/components/icons/LucideIcons.tsx new file mode 100644 index 0000000..83759df --- /dev/null +++ b/frontend/src/components/icons/LucideIcons.tsx @@ -0,0 +1,10 @@ +import { ChevronDown, ChevronRight } from 'lucide-react' + +export { ChevronDown, ChevronRight } + +const LucideIcons = { + ChevronDown, + ChevronRight, +} + +export default LucideIcons \ No newline at end of file diff --git a/frontend/src/components/ui/Badge.tsx b/frontend/src/components/ui/Badge.tsx new file mode 100644 index 0000000..560b3d5 --- /dev/null +++ b/frontend/src/components/ui/Badge.tsx @@ -0,0 +1,23 @@ +import { ServerStatus } from '@/types' + +interface BadgeProps { + status: ServerStatus +} + +const Badge = ({ status }: BadgeProps) => { + const colors = { + connecting: 'bg-yellow-100 text-yellow-800', + connected: 'bg-green-100 text-green-800', + disconnected: 'bg-red-100 text-red-800', + } + + return ( + + {status} + + ) +} + +export default Badge \ No newline at end of file diff --git a/frontend/src/components/ui/DeleteDialog.tsx b/frontend/src/components/ui/DeleteDialog.tsx new file mode 100644 index 0000000..952d27d --- /dev/null +++ b/frontend/src/components/ui/DeleteDialog.tsx @@ -0,0 +1,37 @@ +interface DeleteDialogProps { + isOpen: boolean + onClose: () => void + onConfirm: () => void + serverName: string +} + +const DeleteDialog = ({ isOpen, onClose, onConfirm, serverName }: DeleteDialogProps) => { + if (!isOpen) return null + + return ( +
+
+

Delete Server

+

+ Are you sure you want to delete server {serverName}? This action cannot be undone. +

+
+ + +
+
+
+ ) +} + +export default DeleteDialog \ No newline at end of file diff --git a/frontend/src/components/ui/ToolCard.tsx b/frontend/src/components/ui/ToolCard.tsx new file mode 100644 index 0000000..9166538 --- /dev/null +++ b/frontend/src/components/ui/ToolCard.tsx @@ -0,0 +1,38 @@ +import { useState } from 'react' +import { Tool } from '@/types' +import { ChevronDown, ChevronRight } from '@/components/icons/LucideIcons' + +interface ToolCardProps { + tool: Tool +} + +const ToolCard = ({ tool }: ToolCardProps) => { + const [isExpanded, setIsExpanded] = useState(false) + + return ( +
+
setIsExpanded(!isExpanded)} + > +

{tool.name}

+ +
+ {isExpanded && ( +
+

{tool.description || 'No description available'}

+
+

Input Schema:

+
+              {JSON.stringify(tool.inputSchema, null, 2)}
+            
+
+
+ )} +
+ ) +} + +export default ToolCard \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css new file mode 100644 index 0000000..a965c8f --- /dev/null +++ b/frontend/src/index.css @@ -0,0 +1,20 @@ +/* Use standard Tailwind directives */ +@import "tailwindcss"; + +/* Add some custom styles to verify CSS is working correctly */ +body { + margin: 0; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', + 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', + sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.bg-custom-blue { + background-color: #4a90e2; +} + +.text-custom-white { + color: #ffffff; +} \ No newline at end of file diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx new file mode 100644 index 0000000..2fbbbc1 --- /dev/null +++ b/frontend/src/main.tsx @@ -0,0 +1,10 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import App from './App' +import './index.css' + +ReactDOM.createRoot(document.getElementById('root')!).render( + + + , +) \ No newline at end of file diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts new file mode 100644 index 0000000..014b944 --- /dev/null +++ b/frontend/src/types/index.ts @@ -0,0 +1,55 @@ +// 服务器状态类型 +export type ServerStatus = 'connecting' | 'connected' | 'disconnected'; + +// 工具输入模式类型 +export interface ToolInputSchema { + type: string; + properties?: Record; + required?: string[]; + [key: string]: any; +} + +// 工具类型 +export interface Tool { + name: string; + description?: string; + inputSchema: ToolInputSchema; +} + +// 服务器配置类型 +export interface ServerConfig { + url?: string; + command?: string; + args?: string[] | string; + env?: Record; +} + +// 服务器类型 +export interface Server { + name: string; + status: ServerStatus; + tools?: Tool[]; + config?: ServerConfig; +} + +// 环境变量类型 +export interface EnvVar { + key: string; + value: string; +} + +// 表单数据类型 +export interface ServerFormData { + name: string; + url: string; + command: string; + arguments: string; + args: string[]; +} + +// API响应类型 +export interface ApiResponse { + success: boolean; + data: T; + message?: string; +} \ No newline at end of file diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..e91ebaa --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,31 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "noFallthroughCasesInSwitch": true, + + /* Paths */ + "baseUrl": ".", + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["src"], + "references": [{ "path": "./tsconfig.node.json" }] +} \ No newline at end of file diff --git a/frontend/tsconfig.node.json b/frontend/tsconfig.node.json new file mode 100644 index 0000000..099658c --- /dev/null +++ b/frontend/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} \ No newline at end of file diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..853e72c --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,22 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import tailwindcss from '@tailwindcss/vite'; + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react(), tailwindcss()], + resolve: { + alias: { + '@': path.resolve(__dirname, './src'), + }, + }, + server: { + proxy: { + '/api': { + target: 'http://localhost:3000', + changeOrigin: true, + }, + }, + }, +}); diff --git a/package.json b/package.json index b8aa8e7..947cd22 100644 --- a/package.json +++ b/package.json @@ -7,10 +7,14 @@ "scripts": { "build": "tsc", "start": "node dist/index.js", - "dev": "tsx watch src/index.ts", + "backend:dev": "tsx watch src/index.ts", "lint": "eslint . --ext .ts", "format": "prettier --write \"src/**/*.ts\"", - "test": "jest" + "test": "jest", + "frontend:dev": "cd frontend && vite", + "frontend:build": "cd frontend && vite build", + "frontend:preview": "cd frontend && vite preview", + "dev": "concurrently \"pnpm backend:dev\" \"pnpm frontend:dev\"" }, "keywords": [ "typescript", @@ -23,6 +27,7 @@ "@radix-ui/react-accordion": "^1.2.3", "@radix-ui/react-slot": "^1.1.2", "@shadcn/ui": "^0.0.4", + "@tailwindcss/vite": "^4.1.3", "@types/react": "^19.0.12", "@types/react-dom": "^19.0.4", "autoprefixer": "^10.4.21", @@ -41,17 +46,21 @@ "zod": "^3.24.2" }, "devDependencies": { + "@tailwindcss/postcss": "^4.1.3", "@types/express": "^4.17.21", "@types/jest": "^29.5.5", "@types/node": "^20.8.2", "@typescript-eslint/eslint-plugin": "^6.7.4", "@typescript-eslint/parser": "^6.7.4", + "@vitejs/plugin-react": "^4.2.1", + "concurrently": "^8.2.2", "eslint": "^8.50.0", "jest": "^29.7.0", "prettier": "^3.0.3", "ts-jest": "^29.1.1", "ts-node-dev": "^2.0.0", "tsx": "^4.7.0", - "typescript": "^5.2.2" + "typescript": "^5.2.2", + "vite": "^5.2.6" } -} +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c9760a4..a7a514e 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,6 +20,9 @@ importers: '@shadcn/ui': specifier: ^0.0.4 version: 0.0.4 + '@tailwindcss/vite': + specifier: ^4.1.3 + version: 4.1.3(vite@5.4.17(@types/node@20.17.28)(lightningcss@1.29.2)) '@types/react': specifier: ^19.0.12 version: 19.0.12 @@ -69,6 +72,9 @@ importers: specifier: ^3.24.2 version: 3.24.2 devDependencies: + '@tailwindcss/postcss': + specifier: ^4.1.3 + version: 4.1.3 '@types/express': specifier: ^4.17.21 version: 4.17.21 @@ -84,6 +90,12 @@ importers: '@typescript-eslint/parser': specifier: ^6.7.4 version: 6.21.0(eslint@8.57.1)(typescript@5.8.2) + '@vitejs/plugin-react': + specifier: ^4.2.1 + version: 4.3.4(vite@5.4.17(@types/node@20.17.28)(lightningcss@1.29.2)) + concurrently: + specifier: ^8.2.2 + version: 8.2.2 eslint: specifier: ^8.50.0 version: 8.57.1 @@ -105,9 +117,16 @@ importers: typescript: specifier: ^5.2.2 version: 5.8.2 + vite: + specifier: ^5.2.6 + version: 5.4.17(@types/node@20.17.28)(lightningcss@1.29.2) packages: + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + '@ampproject/remapping@2.3.0': resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} engines: {node: '>=6.0.0'} @@ -258,6 +277,22 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 + '@babel/plugin-transform-react-jsx-self@7.25.9': + resolution: {integrity: sha512-y8quW6p0WHkEhmErnfe58r7x0A70uKphQm8Sp8cV7tjNQwK56sNVK0M73LK3WuYmsuyrftut4xAkjjgU0twaMg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.25.9': + resolution: {integrity: sha512-+iqjT8xmXhhYv4/uiYd8FNQsraMFZIfxVSqxxVSZP0WbbSAWvBXAul0m/zu+7Vv4O/3WtApy9pmaTMiumEZgfg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.27.0': + resolution: {integrity: sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==} + engines: {node: '>=6.9.0'} + '@babel/template@7.27.0': resolution: {integrity: sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==} engines: {node: '>=6.9.0'} @@ -280,102 +315,204 @@ packages: '@emnapi/runtime@1.4.0': resolution: {integrity: sha512-64WYIf4UYcdLnbKn/umDlNjQDSS8AgZrI/R9+x5ilkUVFxXcA1Ebl+gQLc/6mERA4407Xof0R7wEyEuj091CVw==} + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + '@esbuild/aix-ppc64@0.25.2': resolution: {integrity: sha512-wCIboOL2yXZym2cgm6mlA742s9QeJ8DjGVaL39dLN4rRwrOgOyYSnOaFPhKZGLb2ngj4EyfAFjsNJwPXZvseag==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + '@esbuild/android-arm64@0.25.2': resolution: {integrity: sha512-5ZAX5xOmTligeBaeNEPnPaeEuah53Id2tX4c2CVP3JaROTH+j4fnfHCkr1PjXMd78hMst+TlkfKcW/DlTq0i4w==} engines: {node: '>=18'} cpu: [arm64] os: [android] + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + '@esbuild/android-arm@0.25.2': resolution: {integrity: sha512-NQhH7jFstVY5x8CKbcfa166GoV0EFkaPkCKBQkdPJFvo5u+nGXLEH/ooniLb3QI8Fk58YAx7nsPLozUWfCBOJA==} engines: {node: '>=18'} cpu: [arm] os: [android] + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + '@esbuild/android-x64@0.25.2': resolution: {integrity: sha512-Ffcx+nnma8Sge4jzddPHCZVRvIfQ0kMsUsCMcJRHkGJ1cDmhe4SsrYIjLUKn1xpHZybmOqCWwB0zQvsjdEHtkg==} engines: {node: '>=18'} cpu: [x64] os: [android] + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + '@esbuild/darwin-arm64@0.25.2': resolution: {integrity: sha512-MpM6LUVTXAzOvN4KbjzU/q5smzryuoNjlriAIx+06RpecwCkL9JpenNzpKd2YMzLJFOdPqBpuub6eVRP5IgiSA==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + '@esbuild/darwin-x64@0.25.2': resolution: {integrity: sha512-5eRPrTX7wFyuWe8FqEFPG2cU0+butQQVNcT4sVipqjLYQjjh8a8+vUTfgBKM88ObB85ahsnTwF7PSIt6PG+QkA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + '@esbuild/freebsd-arm64@0.25.2': resolution: {integrity: sha512-mLwm4vXKiQ2UTSX4+ImyiPdiHjiZhIaE9QvC7sw0tZ6HoNMjYAqQpGyui5VRIi5sGd+uWq940gdCbY3VLvsO1w==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + '@esbuild/freebsd-x64@0.25.2': resolution: {integrity: sha512-6qyyn6TjayJSwGpm8J9QYYGQcRgc90nmfdUb0O7pp1s4lTY+9D0H9O02v5JqGApUyiHOtkz6+1hZNvNtEhbwRQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + '@esbuild/linux-arm64@0.25.2': resolution: {integrity: sha512-gq/sjLsOyMT19I8obBISvhoYiZIAaGF8JpeXu1u8yPv8BE5HlWYobmlsfijFIZ9hIVGYkbdFhEqC0NvM4kNO0g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + '@esbuild/linux-arm@0.25.2': resolution: {integrity: sha512-UHBRgJcmjJv5oeQF8EpTRZs/1knq6loLxTsjc3nxO9eXAPDLcWW55flrMVc97qFPbmZP31ta1AZVUKQzKTzb0g==} engines: {node: '>=18'} cpu: [arm] os: [linux] + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + '@esbuild/linux-ia32@0.25.2': resolution: {integrity: sha512-bBYCv9obgW2cBP+2ZWfjYTU+f5cxRoGGQ5SeDbYdFCAZpYWrfjjfYwvUpP8MlKbP0nwZ5gyOU/0aUzZ5HWPuvQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + '@esbuild/linux-loong64@0.25.2': resolution: {integrity: sha512-SHNGiKtvnU2dBlM5D8CXRFdd+6etgZ9dXfaPCeJtz+37PIUlixvlIhI23L5khKXs3DIzAn9V8v+qb1TRKrgT5w==} engines: {node: '>=18'} cpu: [loong64] os: [linux] + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + '@esbuild/linux-mips64el@0.25.2': resolution: {integrity: sha512-hDDRlzE6rPeoj+5fsADqdUZl1OzqDYow4TB4Y/3PlKBD0ph1e6uPHzIQcv2Z65u2K0kpeByIyAjCmjn1hJgG0Q==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + '@esbuild/linux-ppc64@0.25.2': resolution: {integrity: sha512-tsHu2RRSWzipmUi9UBDEzc0nLc4HtpZEI5Ba+Omms5456x5WaNuiG3u7xh5AO6sipnJ9r4cRWQB2tUjPyIkc6g==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + '@esbuild/linux-riscv64@0.25.2': resolution: {integrity: sha512-k4LtpgV7NJQOml/10uPU0s4SAXGnowi5qBSjaLWMojNCUICNu7TshqHLAEbkBdAszL5TabfvQ48kK84hyFzjnw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + '@esbuild/linux-s390x@0.25.2': resolution: {integrity: sha512-GRa4IshOdvKY7M/rDpRR3gkiTNp34M0eLTaC1a08gNrh4u488aPhuZOCpkF6+2wl3zAN7L7XIpOFBhnaE3/Q8Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + '@esbuild/linux-x64@0.25.2': resolution: {integrity: sha512-QInHERlqpTTZ4FRB0fROQWXcYRD64lAoiegezDunLpalZMjcUcld3YzZmVJ2H/Cp0wJRZ8Xtjtj0cEHhYc/uUg==} engines: {node: '>=18'} @@ -388,6 +525,12 @@ packages: cpu: [arm64] os: [netbsd] + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + '@esbuild/netbsd-x64@0.25.2': resolution: {integrity: sha512-voZT9Z+tpOxrvfKFyfDYPc4DO4rk06qamv1a/fkuzHpiVBMOhpjK+vBmWM8J1eiB3OLSMFYNaOaBNLXGChf5tg==} engines: {node: '>=18'} @@ -400,30 +543,60 @@ packages: cpu: [arm64] os: [openbsd] + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + '@esbuild/openbsd-x64@0.25.2': resolution: {integrity: sha512-t/TkWwahkH0Tsgoq1Ju7QfgGhArkGLkF1uYz8nQS/PPFlXbP5YgRpqQR3ARRiC2iXoLTWFxc6DJMSK10dVXluw==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + '@esbuild/sunos-x64@0.25.2': resolution: {integrity: sha512-cfZH1co2+imVdWCjd+D1gf9NjkchVhhdpgb1q5y6Hcv9TP6Zi9ZG/beI3ig8TvwT9lH9dlxLq5MQBBgwuj4xvA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + '@esbuild/win32-arm64@0.25.2': resolution: {integrity: sha512-7Loyjh+D/Nx/sOTzV8vfbB3GJuHdOQyrOryFdZvPHLf42Tk9ivBU5Aedi7iyX+x6rbn2Mh68T4qq1SDqJBQO5Q==} engines: {node: '>=18'} cpu: [arm64] os: [win32] + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + '@esbuild/win32-ia32@0.25.2': resolution: {integrity: sha512-WRJgsz9un0nqZJ4MfhabxaD9Ft8KioqU3JMinOTvobbX6MOSUigSBlogP8QB3uxpJDsFS6yN+3FDBdqE5lg9kg==} engines: {node: '>=18'} cpu: [ia32] os: [win32] + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + '@esbuild/win32-x64@0.25.2': resolution: {integrity: sha512-kM3HKb16VIXZyIeVrM1ygYmZBKybX8N4p754bw390wGO3Tf2j4L2/WYL+4suWujpgf6GBYs3jv7TyUivdd05JA==} engines: {node: '>=18'} @@ -487,67 +660,79 @@ packages: resolution: {integrity: sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-arm@1.0.5': resolution: {integrity: sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-s390x@1.0.4': resolution: {integrity: sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-libvips-linux-x64@1.0.4': resolution: {integrity: sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-libvips-linuxmusl-arm64@1.0.4': resolution: {integrity: sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-libvips-linuxmusl-x64@1.0.4': resolution: {integrity: sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-linux-arm64@0.33.5': resolution: {integrity: sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [glibc] '@img/sharp-linux-arm@0.33.5': resolution: {integrity: sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm] os: [linux] + libc: [glibc] '@img/sharp-linux-s390x@0.33.5': resolution: {integrity: sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [s390x] os: [linux] + libc: [glibc] '@img/sharp-linux-x64@0.33.5': resolution: {integrity: sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [glibc] '@img/sharp-linuxmusl-arm64@0.33.5': resolution: {integrity: sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [arm64] os: [linux] + libc: [musl] '@img/sharp-linuxmusl-x64@0.33.5': resolution: {integrity: sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} cpu: [x64] os: [linux] + libc: [musl] '@img/sharp-wasm32@0.33.5': resolution: {integrity: sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==} @@ -685,24 +870,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@next/swc-linux-arm64-musl@15.2.4': resolution: {integrity: sha512-Oioa0SORWLwi35/kVB8aCk5Uq+5/ZIumMK1kJV+jSdazFm2NzPDztsefzdmzzpx5oGCJ6FkUC7vkaUseNTStNA==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@next/swc-linux-x64-gnu@15.2.4': resolution: {integrity: sha512-yb5WTRaHdkgOqFOZiu6rHV1fAEK0flVpaIN2HB6kxHVSy/dIajWbThS7qON3W9/SNOH2JWkVCyulgGYekMePuw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@next/swc-linux-x64-musl@15.2.4': resolution: {integrity: sha512-Dcdv/ix6srhkM25fgXiyOieFUkz+fOYkHlydWCtB0xMST6X9XYI3yPDKBZt1xuhOytONsIFJFB08xXYsxUwJLw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@next/swc-win32-arm64-msvc@15.2.4': resolution: {integrity: sha512-dW0i7eukvDxtIhCYkMrZNQfNicPDExt2jPb9AZPpL7cfyUo7QSNl1DjsHjmmKp6qNAqUESyT8YFl/Aw91cNJJg==} @@ -868,6 +1057,117 @@ packages: '@types/react': optional: true + '@rollup/rollup-android-arm-eabi@4.39.0': + resolution: {integrity: sha512-lGVys55Qb00Wvh8DMAocp5kIcaNzEFTmGhfFd88LfaogYTRKrdxgtlO5H6S49v2Nd8R2C6wLOal0qv6/kCkOwA==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.39.0': + resolution: {integrity: sha512-It9+M1zE31KWfqh/0cJLrrsCPiF72PoJjIChLX+rEcujVRCb4NLQ5QzFkzIZW8Kn8FTbvGQBY5TkKBau3S8cCQ==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.39.0': + resolution: {integrity: sha512-lXQnhpFDOKDXiGxsU9/l8UEGGM65comrQuZ+lDcGUx+9YQ9dKpF3rSEGepyeR5AHZ0b5RgiligsBhWZfSSQh8Q==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.39.0': + resolution: {integrity: sha512-mKXpNZLvtEbgu6WCkNij7CGycdw9cJi2k9v0noMb++Vab12GZjFgUXD69ilAbBh034Zwn95c2PNSz9xM7KYEAQ==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.39.0': + resolution: {integrity: sha512-jivRRlh2Lod/KvDZx2zUR+I4iBfHcu2V/BA2vasUtdtTN2Uk3jfcZczLa81ESHZHPHy4ih3T/W5rPFZ/hX7RtQ==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.39.0': + resolution: {integrity: sha512-8RXIWvYIRK9nO+bhVz8DwLBepcptw633gv/QT4015CpJ0Ht8punmoHU/DuEd3iw9Hr8UwUV+t+VNNuZIWYeY7Q==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + resolution: {integrity: sha512-mz5POx5Zu58f2xAG5RaRRhp3IZDK7zXGk5sdEDj4o96HeaXhlUwmLFzNlc4hCQi5sGdR12VDgEUqVSHer0lI9g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + resolution: {integrity: sha512-+YDwhM6gUAyakl0CD+bMFpdmwIoRDzZYaTWV3SDRBGkMU/VpIBYXXEvkEcTagw/7VVkL2vA29zU4UVy1mP0/Yw==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + resolution: {integrity: sha512-EKf7iF7aK36eEChvlgxGnk7pdJfzfQbNvGV/+l98iiMwU23MwvmV0Ty3pJ0p5WQfm3JRHOytSIqD9LB7Bq7xdQ==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.39.0': + resolution: {integrity: sha512-vYanR6MtqC7Z2SNr8gzVnzUul09Wi1kZqJaek3KcIlI/wq5Xtq4ZPIZ0Mr/st/sv/NnaPwy/D4yXg5x0B3aUUA==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + resolution: {integrity: sha512-NMRUT40+h0FBa5fb+cpxtZoGAggRem16ocVKIv5gDB5uLDgBIwrIsXlGqYbLwW8YyO3WVTk1FkFDjMETYlDqiw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + resolution: {integrity: sha512-0pCNnmxgduJ3YRt+D+kJ6Ai/r+TaePu9ZLENl+ZDV/CdVczXl95CbIiwwswu4L+K7uOIGf6tMo2vm8uadRaICQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + resolution: {integrity: sha512-t7j5Zhr7S4bBtksT73bO6c3Qa2AV/HqiGlj9+KB3gNF5upcVkx+HLgxTm8DK4OkzsOYqbdqbLKwvGMhylJCPhQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + resolution: {integrity: sha512-m6cwI86IvQ7M93MQ2RF5SP8tUjD39Y7rjb1qjHgYh28uAPVU8+k/xYWvxRO3/tBN2pZkSMa5RjnPuUIbrwVxeA==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + resolution: {integrity: sha512-iRDJd2ebMunnk2rsSBYlsptCyuINvxUfGwOUldjv5M4tpa93K8tFMeYGpNk2+Nxl+OBJnBzy2/JCscGeO507kA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.39.0': + resolution: {integrity: sha512-t9jqYw27R6Lx0XKfEFe5vUeEJ5pF3SGIM6gTfONSMb7DuG6z6wfj2yjcoZxHg129veTqU7+wOhY6GX8wmf90dA==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.39.0': + resolution: {integrity: sha512-ThFdkrFDP55AIsIZDKSBWEt/JcWlCzydbZHinZ0F/r1h83qbGeenCt/G/wG2O0reuENDD2tawfAj2s8VK7Bugg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + resolution: {integrity: sha512-jDrLm6yUtbOg2TYB3sBF3acUnAwsIksEYjLeHL+TJv9jg+TmTwdyjnDex27jqEMakNKf3RwwPahDIt7QXCSqRQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + resolution: {integrity: sha512-6w9uMuza+LbLCVoNKL5FSLE7yvYkq9laSd09bwS0tMjkwXrmib/4KmoJcrKhLWHvw19mwU+33ndC69T7weNNjQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.39.0': + resolution: {integrity: sha512-yAkUOkIKZlK5dl7u6dg897doBgLXmUHhIINM2c+sND3DZwnrdQkkSiDh7N75Ll4mM4dxSkYfXqU9fW3lLkMFug==} + cpu: [x64] + os: [win32] + '@shadcn/ui@0.0.4': resolution: {integrity: sha512-0dtu/5ApsOZ24qgaZwtif8jVwqol7a4m1x5AxPuM1k5wxhqU7t/qEfBGtaSki1R8VlbTQfCj5PAlO45NKCa7Gg==} hasBin: true @@ -887,6 +1187,91 @@ packages: '@swc/helpers@0.5.15': resolution: {integrity: sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==} + '@tailwindcss/node@4.1.3': + resolution: {integrity: sha512-H/6r6IPFJkCfBJZ2dKZiPJ7Ueb2wbL592+9bQEl2r73qbX6yGnmQVIfiUvDRB2YI0a3PWDrzUwkvQx1XW1bNkA==} + + '@tailwindcss/oxide-android-arm64@4.1.3': + resolution: {integrity: sha512-cxklKjtNLwFl3mDYw4XpEfBY+G8ssSg9ADL4Wm6//5woi3XGqlxFsnV5Zb6v07dxw1NvEX2uoqsxO/zWQsgR+g==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.3': + resolution: {integrity: sha512-mqkf2tLR5VCrjBvuRDwzKNShRu99gCAVMkVsaEOFvv6cCjlEKXRecPu9DEnxp6STk5z+Vlbh1M5zY3nQCXMXhw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.3': + resolution: {integrity: sha512-7sGraGaWzXvCLyxrc7d+CCpUN3fYnkkcso3rCzwUmo/LteAl2ZGCDlGvDD8Y/1D3ngxT8KgDj1DSwOnNewKhmg==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.3': + resolution: {integrity: sha512-E2+PbcbzIReaAYZe997wb9rId246yDkCwAakllAWSGqe6VTg9hHle67hfH6ExjpV2LSK/siRzBUs5wVff3RW9w==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3': + resolution: {integrity: sha512-GvfbJ8wjSSjbLFFE3UYz4Eh8i4L6GiEYqCtA8j2Zd2oXriPuom/Ah/64pg/szWycQpzRnbDiJozoxFU2oJZyfg==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.3': + resolution: {integrity: sha512-35UkuCWQTeG9BHcBQXndDOrpsnt3Pj9NVIB4CgNiKmpG8GnCNXeMczkUpOoqcOhO6Cc/mM2W7kaQ/MTEENDDXg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.3': + resolution: {integrity: sha512-dm18aQiML5QCj9DQo7wMbt1Z2tl3Giht54uVR87a84X8qRtuXxUqnKQkRDK5B4bCOmcZ580lF9YcoMkbDYTXHQ==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.3': + resolution: {integrity: sha512-LMdTmGe/NPtGOaOfV2HuO7w07jI3cflPrVq5CXl+2O93DCewADK0uW1ORNAcfu2YxDUS035eY2W38TxrsqngxA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.1.3': + resolution: {integrity: sha512-aalNWwIi54bbFEizwl1/XpmdDrOaCjRFQRgtbv9slWjmNPuJJTIKPHf5/XXDARc9CneW9FkSTqTbyvNecYAEGw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.3': + resolution: {integrity: sha512-PEj7XR4OGTGoboTIAdXicKuWl4EQIjKHKuR+bFy9oYN7CFZo0eu74+70O4XuERX4yjqVZGAkCdglBODlgqcCXg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.3': + resolution: {integrity: sha512-T8gfxECWDBENotpw3HR9SmNiHC9AOJdxs+woasRZ8Q/J4VHN0OMs7F+4yVNZ9EVN26Wv6mZbK0jv7eHYuLJLwA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.3': + resolution: {integrity: sha512-t16lpHCU7LBxDe/8dCj9ntyNpXaSTAgxWm1u2XQP5NiIu4KGSyrDJJRlK9hJ4U9yJxx0UKCVI67MJWFNll5mOQ==} + engines: {node: '>= 10'} + + '@tailwindcss/postcss@4.1.3': + resolution: {integrity: sha512-6s5nJODm98F++QT49qn8xJKHQRamhYHfMi3X7/ltxiSQ9dyRsaFSfFkfaMsanWzf+TMYQtbk8mt5f6cCVXJwfg==} + + '@tailwindcss/vite@4.1.3': + resolution: {integrity: sha512-lUI/QaDxLtlV52Lho6pu07CG9pSnRYLOPmKGIQjyHdTBagemc6HmgZxyjGAQ/5HMPrNeWBfTVIpQl0/jLXvWHQ==} + peerDependencies: + vite: ^5.2.0 || ^6 + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -917,6 +1302,9 @@ packages: '@types/connect@3.4.38': resolution: {integrity: sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug==} + '@types/estree@1.0.7': + resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==} + '@types/express-serve-static-core@4.19.6': resolution: {integrity: sha512-N4LZ2xG7DatVqhCZzOGb1Yi5lMbXSZcmdLDe9EzSndPV2HpWYWzRbaerl2n27irrm94EPpprqa8KpskPT085+A==} @@ -1049,6 +1437,12 @@ packages: '@ungap/structured-clone@1.3.0': resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@vitejs/plugin-react@4.3.4': + resolution: {integrity: sha512-SCCPBJtYLdE8PX/7ZQAs1QAZ8Jqwih+0VBLum1EGqmCCQal+MIUqLCzj3ZUy8ufbC0cAM4LRlSTm7IQJwWT4ug==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 + accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -1306,6 +1700,11 @@ packages: concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + concurrently@8.2.2: + resolution: {integrity: sha512-1dP4gpXFhei8IOtlXRE/T/4H88ElHgTiUzh71YUmtjTEHMSRS2Z/fgOxHSxxusGHogsRfxNq1vyAwxSC+EVyDg==} + engines: {node: ^14.13.0 || >=16.0.0} + hasBin: true + content-disposition@0.5.4: resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} engines: {node: '>= 0.6'} @@ -1355,6 +1754,10 @@ packages: resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} engines: {node: '>= 12'} + date-fns@2.30.0: + resolution: {integrity: sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==} + engines: {node: '>=0.11'} + debug@2.6.9: resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} peerDependencies: @@ -1468,6 +1871,10 @@ packages: resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} engines: {node: '>= 0.8'} + enhanced-resolve@5.18.1: + resolution: {integrity: sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==} + engines: {node: '>=10.13.0'} + error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -1483,6 +1890,11 @@ packages: resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} engines: {node: '>= 0.4'} + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + esbuild@0.25.2: resolution: {integrity: sha512-16854zccKPnC+toMywC+uKNeYSv+/eXkevRAfwRD/G9Cleq66m8XFIrigkbvauLLlCfDL45Q2cWegSg53gGBnQ==} engines: {node: '>=18'} @@ -2020,6 +2432,10 @@ packages: node-notifier: optional: true + jiti@2.4.2: + resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==} + hasBin: true + js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -2071,6 +2487,74 @@ packages: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} + lightningcss-darwin-arm64@1.29.2: + resolution: {integrity: sha512-cK/eMabSViKn/PG8U/a7aCorpeKLMlK0bQeNHmdb7qUnBkNPnL+oV5DjJUo0kqWsJUapZsM4jCfYItbqBDvlcA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.29.2: + resolution: {integrity: sha512-j5qYxamyQw4kDXX5hnnCKMf3mLlHvG44f24Qyi2965/Ycz829MYqjrVg2H8BidybHBp9kom4D7DR5VqCKDXS0w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.29.2: + resolution: {integrity: sha512-wDk7M2tM78Ii8ek9YjnY8MjV5f5JN2qNVO+/0BAGZRvXKtQrBC4/cn4ssQIpKIPP44YXw6gFdpUF+Ps+RGsCwg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.29.2: + resolution: {integrity: sha512-IRUrOrAF2Z+KExdExe3Rz7NSTuuJ2HvCGlMKoquK5pjvo2JY4Rybr+NrKnq0U0hZnx5AnGsuFHjGnNT14w26sg==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.29.2: + resolution: {integrity: sha512-KKCpOlmhdjvUTX/mBuaKemp0oeDIBBLFiU5Fnqxh1/DZ4JPZi4evEH7TKoSBFOSOV3J7iEmmBaw/8dpiUvRKlQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.29.2: + resolution: {integrity: sha512-Q64eM1bPlOOUgxFmoPUefqzY1yV3ctFPE6d/Vt7WzLW4rKTv7MyYNky+FWxRpLkNASTnKQUaiMJ87zNODIrrKQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.29.2: + resolution: {integrity: sha512-0v6idDCPG6epLXtBH/RPkHvYx74CVziHo6TMYga8O2EiQApnUPZsbR9nFNrg2cgBzk1AYqEd95TlrsL7nYABQg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.29.2: + resolution: {integrity: sha512-rMpz2yawkgGT8RULc5S4WiZopVMOFWjiItBT7aSfDX4NQav6M44rhn5hjtkKzB+wMTRlLLqxkeYEtQ3dd9696w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.29.2: + resolution: {integrity: sha512-nL7zRW6evGQqYVu/bKGK+zShyz8OVzsCotFgc7judbt6wnB2KbiKKJwBE4SGoDBQ1O94RjW4asrCjQL4i8Fhbw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.29.2: + resolution: {integrity: sha512-EdIUW3B2vLuHmv7urfzMI/h2fmlnOQBk1xlsDxkN1tCWKjNFjfLhGxYk8C8mzpSfr+A6jFFIi8fU6LbQGsRWjA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.29.2: + resolution: {integrity: sha512-6b6gd/RUXKaw5keVdSEtqFVdzWnU5jMxTUjA2bVcMNPLwSQ08Sv/UodBVtETLCn7k4S1Ibxwh7k68IwLZPgKaA==} + engines: {node: '>= 12.0.0'} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -2088,6 +2572,9 @@ packages: lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + log-symbols@5.1.0: resolution: {integrity: sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==} engines: {node: '>=12'} @@ -2450,6 +2937,10 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + react@19.1.0: resolution: {integrity: sha512-FS+XFBNvn3GTAWq26joslQgWNoFu08F4kl0J4CgdNKADkdSGXQyTCnKteIAJy96Br6YbpEU1LSzV5dYtjMkMDg==} engines: {node: '>=0.10.0'} @@ -2462,6 +2953,9 @@ packages: resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} engines: {node: '>=8.10.0'} + regenerator-runtime@0.14.1: + resolution: {integrity: sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -2508,6 +3002,11 @@ packages: deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true + rollup@4.39.0: + resolution: {integrity: sha512-thI8kNc02yNvnmJp8dr3fNWJ9tCONDhp6TV35X6HkKGGs9E6q7YWCHbe5vKiTa7TAiNcFEmXKj3X/pG2b3ci0g==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + router@2.2.0: resolution: {integrity: sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==} engines: {node: '>= 18'} @@ -2515,6 +3014,9 @@ packages: run-parallel@1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + rxjs@7.8.2: + resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -2564,6 +3066,10 @@ packages: resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} engines: {node: '>=8'} + shell-quote@1.8.2: + resolution: {integrity: sha512-AzqKpGKjrj7EM6rKVQEPpB288oCfnrEIuyoT9cyF4nmGa7V8Zk6f7RRqYisX8X9m+Q7bd632aZW4ky7EhbQztA==} + engines: {node: '>= 0.4'} + side-channel-list@1.0.0: resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} engines: {node: '>= 0.4'} @@ -2607,6 +3113,9 @@ packages: resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} engines: {node: '>=0.10.0'} + spawn-command@0.0.2: + resolution: {integrity: sha512-zC8zGoGkmc8J9ndvml8Xksr1Amk9qBujgbF0JAIWO7kXr43w0h/0GJNM/Vustixu+YE8N/MTrQ7N31FvHUACxQ==} + sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} @@ -2705,6 +3214,13 @@ packages: tailwindcss@4.0.17: resolution: {integrity: sha512-OErSiGzRa6rLiOvaipsDZvLMSpsBZ4ysB4f0VKGXUrjw2jfkJRd6kjRKV2+ZmTCNvwtvgdDam5D7w6WXsdLJZw==} + tailwindcss@4.1.3: + resolution: {integrity: sha512-2Q+rw9vy1WFXu5cIxlvsabCwhU2qUwodGq03ODhLJ0jW4ek5BUtoCsnLB0qG+m8AHgEsSJcJGDSDe06FXlP74g==} + + tapable@2.2.1: + resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} + engines: {node: '>=6'} + test-exclude@6.0.0: resolution: {integrity: sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==} engines: {node: '>=8'} @@ -2864,6 +3380,37 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite@5.4.17: + resolution: {integrity: sha512-5+VqZryDj4wgCs55o9Lp+p8GE78TLVg0lasCH5xFZ4jacZjtqZa6JUw9/p0WeAojaOfncSM6v77InkFPGnvPvg==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || >=20.0.0 + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + walker@1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} @@ -2931,6 +3478,8 @@ packages: snapshots: + '@alloc/quick-lru@5.2.0': {} + '@ampproject/remapping@2.3.0': dependencies: '@jridgewell/gen-mapping': 0.3.8 @@ -3098,6 +3647,20 @@ snapshots: '@babel/core': 7.26.10 '@babel/helper-plugin-utils': 7.26.5 + '@babel/plugin-transform-react-jsx-self@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/plugin-transform-react-jsx-source@7.25.9(@babel/core@7.26.10)': + dependencies: + '@babel/core': 7.26.10 + '@babel/helper-plugin-utils': 7.26.5 + + '@babel/runtime@7.27.0': + dependencies: + regenerator-runtime: 0.14.1 + '@babel/template@7.27.0': dependencies: '@babel/code-frame': 7.26.2 @@ -3132,78 +3695,147 @@ snapshots: tslib: 2.8.1 optional: true + '@esbuild/aix-ppc64@0.21.5': + optional: true + '@esbuild/aix-ppc64@0.25.2': optional: true + '@esbuild/android-arm64@0.21.5': + optional: true + '@esbuild/android-arm64@0.25.2': optional: true + '@esbuild/android-arm@0.21.5': + optional: true + '@esbuild/android-arm@0.25.2': optional: true + '@esbuild/android-x64@0.21.5': + optional: true + '@esbuild/android-x64@0.25.2': optional: true + '@esbuild/darwin-arm64@0.21.5': + optional: true + '@esbuild/darwin-arm64@0.25.2': optional: true + '@esbuild/darwin-x64@0.21.5': + optional: true + '@esbuild/darwin-x64@0.25.2': optional: true + '@esbuild/freebsd-arm64@0.21.5': + optional: true + '@esbuild/freebsd-arm64@0.25.2': optional: true + '@esbuild/freebsd-x64@0.21.5': + optional: true + '@esbuild/freebsd-x64@0.25.2': optional: true + '@esbuild/linux-arm64@0.21.5': + optional: true + '@esbuild/linux-arm64@0.25.2': optional: true + '@esbuild/linux-arm@0.21.5': + optional: true + '@esbuild/linux-arm@0.25.2': optional: true + '@esbuild/linux-ia32@0.21.5': + optional: true + '@esbuild/linux-ia32@0.25.2': optional: true + '@esbuild/linux-loong64@0.21.5': + optional: true + '@esbuild/linux-loong64@0.25.2': optional: true + '@esbuild/linux-mips64el@0.21.5': + optional: true + '@esbuild/linux-mips64el@0.25.2': optional: true + '@esbuild/linux-ppc64@0.21.5': + optional: true + '@esbuild/linux-ppc64@0.25.2': optional: true + '@esbuild/linux-riscv64@0.21.5': + optional: true + '@esbuild/linux-riscv64@0.25.2': optional: true + '@esbuild/linux-s390x@0.21.5': + optional: true + '@esbuild/linux-s390x@0.25.2': optional: true + '@esbuild/linux-x64@0.21.5': + optional: true + '@esbuild/linux-x64@0.25.2': optional: true '@esbuild/netbsd-arm64@0.25.2': optional: true + '@esbuild/netbsd-x64@0.21.5': + optional: true + '@esbuild/netbsd-x64@0.25.2': optional: true '@esbuild/openbsd-arm64@0.25.2': optional: true + '@esbuild/openbsd-x64@0.21.5': + optional: true + '@esbuild/openbsd-x64@0.25.2': optional: true + '@esbuild/sunos-x64@0.21.5': + optional: true + '@esbuild/sunos-x64@0.25.2': optional: true + '@esbuild/win32-arm64@0.21.5': + optional: true + '@esbuild/win32-arm64@0.25.2': optional: true + '@esbuild/win32-ia32@0.21.5': + optional: true + '@esbuild/win32-ia32@0.25.2': optional: true + '@esbuild/win32-x64@0.21.5': + optional: true + '@esbuild/win32-x64@0.25.2': optional: true @@ -3681,6 +4313,66 @@ snapshots: optionalDependencies: '@types/react': 19.0.12 + '@rollup/rollup-android-arm-eabi@4.39.0': + optional: true + + '@rollup/rollup-android-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.39.0': + optional: true + + '@rollup/rollup-darwin-x64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.39.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-loongarch64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-powerpc64le-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.39.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.39.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.39.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.39.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.39.0': + optional: true + '@shadcn/ui@0.0.4': dependencies: chalk: 5.2.0 @@ -3708,6 +4400,75 @@ snapshots: dependencies: tslib: 2.8.1 + '@tailwindcss/node@4.1.3': + dependencies: + enhanced-resolve: 5.18.1 + jiti: 2.4.2 + lightningcss: 1.29.2 + tailwindcss: 4.1.3 + + '@tailwindcss/oxide-android-arm64@4.1.3': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.3': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.3': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.3': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.3': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.3': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.3': + optional: true + + '@tailwindcss/oxide@4.1.3': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.3 + '@tailwindcss/oxide-darwin-arm64': 4.1.3 + '@tailwindcss/oxide-darwin-x64': 4.1.3 + '@tailwindcss/oxide-freebsd-x64': 4.1.3 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.3 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.3 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.3 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.3 + '@tailwindcss/oxide-linux-x64-musl': 4.1.3 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.3 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.3 + + '@tailwindcss/postcss@4.1.3': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.1.3 + '@tailwindcss/oxide': 4.1.3 + postcss: 8.5.3 + tailwindcss: 4.1.3 + + '@tailwindcss/vite@4.1.3(vite@5.4.17(@types/node@20.17.28)(lightningcss@1.29.2))': + dependencies: + '@tailwindcss/node': 4.1.3 + '@tailwindcss/oxide': 4.1.3 + tailwindcss: 4.1.3 + vite: 5.4.17(@types/node@20.17.28)(lightningcss@1.29.2) + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} @@ -3746,6 +4507,8 @@ snapshots: dependencies: '@types/node': 20.17.28 + '@types/estree@1.0.7': {} + '@types/express-serve-static-core@4.19.6': dependencies: '@types/node': 20.17.28 @@ -3914,6 +4677,17 @@ snapshots: '@ungap/structured-clone@1.3.0': {} + '@vitejs/plugin-react@4.3.4(vite@5.4.17(@types/node@20.17.28)(lightningcss@1.29.2))': + dependencies: + '@babel/core': 7.26.10 + '@babel/plugin-transform-react-jsx-self': 7.25.9(@babel/core@7.26.10) + '@babel/plugin-transform-react-jsx-source': 7.25.9(@babel/core@7.26.10) + '@types/babel__core': 7.20.5 + react-refresh: 0.14.2 + vite: 5.4.17(@types/node@20.17.28)(lightningcss@1.29.2) + transitivePeerDependencies: + - supports-color + accepts@1.3.8: dependencies: mime-types: 2.1.35 @@ -4214,6 +4988,18 @@ snapshots: concat-map@0.0.1: {} + concurrently@8.2.2: + dependencies: + chalk: 4.1.2 + date-fns: 2.30.0 + lodash: 4.17.21 + rxjs: 7.8.2 + shell-quote: 1.8.2 + spawn-command: 0.0.2 + supports-color: 8.1.1 + tree-kill: 1.2.2 + yargs: 17.7.2 + content-disposition@0.5.4: dependencies: safe-buffer: 5.2.1 @@ -4264,6 +5050,10 @@ snapshots: data-uri-to-buffer@4.0.1: {} + date-fns@2.30.0: + dependencies: + '@babel/runtime': 7.27.0 + debug@2.6.9: dependencies: ms: 2.0.0 @@ -4290,8 +5080,7 @@ snapshots: destroy@1.2.0: {} - detect-libc@2.0.3: - optional: true + detect-libc@2.0.3: {} detect-newline@3.1.0: {} @@ -4335,6 +5124,11 @@ snapshots: encodeurl@2.0.0: {} + enhanced-resolve@5.18.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.2.1 + error-ex@1.3.2: dependencies: is-arrayish: 0.2.1 @@ -4347,6 +5141,32 @@ snapshots: dependencies: es-errors: 1.3.0 + esbuild@0.21.5: + optionalDependencies: + '@esbuild/aix-ppc64': 0.21.5 + '@esbuild/android-arm': 0.21.5 + '@esbuild/android-arm64': 0.21.5 + '@esbuild/android-x64': 0.21.5 + '@esbuild/darwin-arm64': 0.21.5 + '@esbuild/darwin-x64': 0.21.5 + '@esbuild/freebsd-arm64': 0.21.5 + '@esbuild/freebsd-x64': 0.21.5 + '@esbuild/linux-arm': 0.21.5 + '@esbuild/linux-arm64': 0.21.5 + '@esbuild/linux-ia32': 0.21.5 + '@esbuild/linux-loong64': 0.21.5 + '@esbuild/linux-mips64el': 0.21.5 + '@esbuild/linux-ppc64': 0.21.5 + '@esbuild/linux-riscv64': 0.21.5 + '@esbuild/linux-s390x': 0.21.5 + '@esbuild/linux-x64': 0.21.5 + '@esbuild/netbsd-x64': 0.21.5 + '@esbuild/openbsd-x64': 0.21.5 + '@esbuild/sunos-x64': 0.21.5 + '@esbuild/win32-arm64': 0.21.5 + '@esbuild/win32-ia32': 0.21.5 + '@esbuild/win32-x64': 0.21.5 + esbuild@0.25.2: optionalDependencies: '@esbuild/aix-ppc64': 0.25.2 @@ -5196,6 +6016,8 @@ snapshots: - supports-color - ts-node + jiti@2.4.2: {} + js-tokens@4.0.0: {} js-yaml@3.14.1: @@ -5238,6 +6060,51 @@ snapshots: prelude-ls: 1.2.1 type-check: 0.4.0 + lightningcss-darwin-arm64@1.29.2: + optional: true + + lightningcss-darwin-x64@1.29.2: + optional: true + + lightningcss-freebsd-x64@1.29.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.29.2: + optional: true + + lightningcss-linux-arm64-gnu@1.29.2: + optional: true + + lightningcss-linux-arm64-musl@1.29.2: + optional: true + + lightningcss-linux-x64-gnu@1.29.2: + optional: true + + lightningcss-linux-x64-musl@1.29.2: + optional: true + + lightningcss-win32-arm64-msvc@1.29.2: + optional: true + + lightningcss-win32-x64-msvc@1.29.2: + optional: true + + lightningcss@1.29.2: + dependencies: + detect-libc: 2.0.3 + optionalDependencies: + lightningcss-darwin-arm64: 1.29.2 + lightningcss-darwin-x64: 1.29.2 + lightningcss-freebsd-x64: 1.29.2 + lightningcss-linux-arm-gnueabihf: 1.29.2 + lightningcss-linux-arm64-gnu: 1.29.2 + lightningcss-linux-arm64-musl: 1.29.2 + lightningcss-linux-x64-gnu: 1.29.2 + lightningcss-linux-x64-musl: 1.29.2 + lightningcss-win32-arm64-msvc: 1.29.2 + lightningcss-win32-x64-msvc: 1.29.2 + lines-and-columns@1.2.4: {} locate-path@5.0.0: @@ -5252,6 +6119,8 @@ snapshots: lodash.merge@4.6.2: {} + lodash@4.17.21: {} + log-symbols@5.1.0: dependencies: chalk: 5.2.0 @@ -5564,6 +6433,8 @@ snapshots: react-is@18.3.1: {} + react-refresh@0.14.2: {} + react@19.1.0: {} readable-stream@3.6.2: @@ -5576,6 +6447,8 @@ snapshots: dependencies: picomatch: 2.3.1 + regenerator-runtime@0.14.1: {} + require-directory@2.1.1: {} resolve-cwd@3.0.0: @@ -5611,6 +6484,32 @@ snapshots: dependencies: glob: 7.2.3 + rollup@4.39.0: + dependencies: + '@types/estree': 1.0.7 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.39.0 + '@rollup/rollup-android-arm64': 4.39.0 + '@rollup/rollup-darwin-arm64': 4.39.0 + '@rollup/rollup-darwin-x64': 4.39.0 + '@rollup/rollup-freebsd-arm64': 4.39.0 + '@rollup/rollup-freebsd-x64': 4.39.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.39.0 + '@rollup/rollup-linux-arm-musleabihf': 4.39.0 + '@rollup/rollup-linux-arm64-gnu': 4.39.0 + '@rollup/rollup-linux-arm64-musl': 4.39.0 + '@rollup/rollup-linux-loongarch64-gnu': 4.39.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-gnu': 4.39.0 + '@rollup/rollup-linux-riscv64-musl': 4.39.0 + '@rollup/rollup-linux-s390x-gnu': 4.39.0 + '@rollup/rollup-linux-x64-gnu': 4.39.0 + '@rollup/rollup-linux-x64-musl': 4.39.0 + '@rollup/rollup-win32-arm64-msvc': 4.39.0 + '@rollup/rollup-win32-ia32-msvc': 4.39.0 + '@rollup/rollup-win32-x64-msvc': 4.39.0 + fsevents: 2.3.3 + router@2.2.0: dependencies: debug: 4.4.0 @@ -5625,6 +6524,10 @@ snapshots: dependencies: queue-microtask: 1.2.3 + rxjs@7.8.2: + dependencies: + tslib: 2.8.1 + safe-buffer@5.2.1: {} safer-buffer@2.1.2: {} @@ -5722,6 +6625,8 @@ snapshots: shebang-regex@3.0.0: {} + shell-quote@1.8.2: {} + side-channel-list@1.0.0: dependencies: es-errors: 1.3.0 @@ -5775,6 +6680,8 @@ snapshots: source-map@0.6.1: {} + spawn-command@0.0.2: {} + sprintf-js@1.0.3: {} stack-utils@2.0.6: @@ -5849,6 +6756,10 @@ snapshots: tailwindcss@4.0.17: {} + tailwindcss@4.1.3: {} + + tapable@2.2.1: {} + test-exclude@6.0.0: dependencies: '@istanbuljs/schema': 0.1.3 @@ -5998,6 +6909,16 @@ snapshots: vary@1.1.2: {} + vite@5.4.17(@types/node@20.17.28)(lightningcss@1.29.2): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.3 + rollup: 4.39.0 + optionalDependencies: + '@types/node': 20.17.28 + fsevents: 2.3.3 + lightningcss: 1.29.2 + walker@1.0.8: dependencies: makeerror: 1.0.12 diff --git a/public/components/DeleteDialog.jsx b/public/components/DeleteDialog.jsx deleted file mode 100644 index 59010ce..0000000 --- a/public/components/DeleteDialog.jsx +++ /dev/null @@ -1,36 +0,0 @@ -// Reusable confirmation dialog component for server deletion -// Props: -// - isOpen: boolean - Controls dialog visibility -// - onClose: () => void - Handler for dialog dismissal -// - onConfirm: () => void - Handler for delete confirmation -// - serverName: string - Name of the server to be deleted -window.DeleteDialog = function DeleteDialog({ isOpen, onClose, onConfirm, serverName }) { - return ( -
-
-
-

Delete Server

-
-

- Are you sure you want to delete server {serverName}? This action cannot be undone. -

-
-
- - -
-
-
-
- ); -} diff --git a/public/components/LucideIcons.jsx b/public/components/LucideIcons.jsx deleted file mode 100644 index f9dc08f..0000000 --- a/public/components/LucideIcons.jsx +++ /dev/null @@ -1,44 +0,0 @@ -// Lightweight implementation of Lucide icons without external dependencies -// Each icon component accepts: -// - size: number (default: 24) - Icon dimensions in pixels -// - className: string - Additional CSS classes - -const ChevronDown = ({ size = 24, className = "" }) => ( - - - -); - -const ChevronRight = ({ size = 24, className = "" }) => ( - - - -); - -// Export icons to global scope for use in other components -window.LucideIcons = { - ChevronDown, - ChevronRight -}; diff --git a/public/index.html b/public/index.html deleted file mode 100644 index 1209fbe..0000000 --- a/public/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - MCP Hub Dashboard - - - - - - - - -
- - - - - \ No newline at end of file diff --git a/public/js/app.js b/public/js/app.js deleted file mode 100644 index 400e9ee..0000000 --- a/public/js/app.js +++ /dev/null @@ -1,677 +0,0 @@ -const { useState, useEffect, Fragment } = React; -const { ChevronDown, ChevronRight } = window.LucideIcons || {}; - -// Status badge component with predefined color schemes -function Badge({ status }) { - const colors = { - connecting: 'bg-yellow-100 text-yellow-800', - connected: 'bg-green-100 text-green-800', - disconnected: 'bg-red-100 text-red-800', - }; - - return ( - - {status} - - ); -} - -// Displays tool details with expandable input schema -function ToolCard({ tool }) { - const [isExpanded, setIsExpanded] = useState(false); - - return ( -
-
setIsExpanded(!isExpanded)} - > -

{tool.name}

- -
- {isExpanded && ( -
-

{tool.description || 'No description available'}

-
-

Input Schema:

-
-              {JSON.stringify(tool.inputSchema, null, 2)}
-            
-
-
- )} -
- ); -} - -// Delete confirmation dialog component -function DeleteDialog({ isOpen, onClose, onConfirm, serverName }) { - if (!isOpen) return null; - - return ( -
-
-

Confirm Deletion

-

- Are you sure you want to delete the server {serverName}? This action - cannot be undone. -

-
- - -
-
-
- ); -} - -// Main server card component for displaying server status and available tools -function ServerCard({ server, onRemove, onEdit }) { - const [isExpanded, setIsExpanded] = useState(false); - const [showDeleteDialog, setShowDeleteDialog] = useState(false); - - const handleRemove = (e) => { - e.stopPropagation(); - setShowDeleteDialog(true); - }; - - const handleEdit = (e) => { - e.stopPropagation(); - onEdit(server); - }; - - const handleConfirmDelete = () => { - onRemove(server.name); - setShowDeleteDialog(false); - }; - - return ( -
-
setIsExpanded(!isExpanded)} - > -
-

{server.name}

- -
-
- - - -
-
- - setShowDeleteDialog(false)} - onConfirm={handleConfirmDelete} - serverName={server.name} - /> - - {isExpanded && server.tools && ( -
-

Available Tools

-
- {server.tools.map((tool, index) => ( - - ))} -
-
- )} -
- ); -} - -// Form component for adding/editing MCP servers with stdio or SSE protocol support -function ServerForm({ onSubmit, onCancel, initialData = null, modalTitle }) { - const [serverType, setServerType] = useState( - initialData && initialData.config && initialData.config.url ? 'sse' : 'stdio', - ); - - const [formData, setFormData] = useState({ - name: (initialData && initialData.name) || '', - url: (initialData && initialData.config && initialData.config.url) || '', - command: (initialData && initialData.config && initialData.config.command) || '', - arguments: - initialData && initialData.config && initialData.config.args - ? Array.isArray(initialData.config.args) - ? initialData.config.args.join(' ') - : String(initialData.config.args) - : '', - args: (initialData && initialData.config && initialData.config.args) || [], - }); - - const [envVars, setEnvVars] = useState( - initialData && initialData.config && initialData.config.env - ? Object.entries(initialData.config.env).map(([key, value]) => ({ key, value })) - : [], - ); - - const [error, setError] = useState(null); - const isEdit = !!initialData; - - const handleInputChange = (e) => { - const { name, value } = e.target; - setFormData({ ...formData, [name]: value }); - }; - - // Transform space-separated arguments string into array - const handleArgsChange = (value) => { - let args = value.split(' ').filter((arg) => arg.trim() !== ''); - setFormData({ ...formData, arguments: value, args }); - }; - - const handleEnvVarChange = (index, field, value) => { - const newEnvVars = [...envVars]; - newEnvVars[index][field] = value; - setEnvVars(newEnvVars); - }; - - const addEnvVar = () => { - setEnvVars([...envVars, { key: '', value: '' }]); - }; - - const removeEnvVar = (index) => { - const newEnvVars = [...envVars]; - newEnvVars.splice(index, 1); - setEnvVars(newEnvVars); - }; - - // Submit handler for server configuration - const handleSubmit = async (e) => { - e.preventDefault(); - setError(null); - - try { - const env = {}; - envVars.forEach(({ key, value }) => { - if (key.trim()) { - env[key.trim()] = value; - } - }); - - const payload = { - name: formData.name, - config: - serverType === 'sse' - ? { url: formData.url } - : { - command: formData.command, - args: formData.args, - env: Object.keys(env).length > 0 ? env : undefined, - }, - }; - - onSubmit(payload); - } catch (err) { - setError('Error: ' + err.message); - } - }; - - return ( -
-
-

{modalTitle}

- -
- - {error &&
{error}
} - -
-
- - -
- -
- -
-
- setServerType('stdio')} - className="mr-1" - /> - -
-
- setServerType('sse')} - className="mr-1" - /> - -
-
-
- - {serverType === 'sse' ? ( -
- - -
- ) : ( - -
- - -
-
- - handleArgsChange(e.target.value)} - className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline" - placeholder="e.g., -y time-mcp" - required={serverType === 'stdio'} - /> -
- -
-
- - -
- {envVars.map((envVar, index) => ( -
-
- handleEnvVarChange(index, 'key', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" - placeholder="key" - /> - : - handleEnvVarChange(index, 'value', e.target.value)} - className="shadow appearance-none border rounded py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline w-1/2" - placeholder="value" - /> -
- -
- ))} -
-
- )} - -
- - -
-
-
- ); -} - -// Form component for adding new MCP servers (wrapper around ServerForm) -function AddServerForm({ onAdd }) { - const [modalVisible, setModalVisible] = useState(false); - - const toggleModal = () => { - setModalVisible(!modalVisible); - }; - - const handleSubmit = async (payload) => { - try { - const response = await fetch('/api/servers', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - - const result = await response.json(); - - if (!response.ok) { - alert(result.message || 'Failed to add server'); - return; - } - - setModalVisible(false); - onAdd(); - } catch (err) { - alert('Error: ' + err.message); - } - }; - - return ( -
- - - {modalVisible && ( -
- -
- )} -
- ); -} - -// Form component for editing MCP servers (wrapper around ServerForm) -function EditServerForm({ server, onEdit, onCancel }) { - const handleSubmit = async (payload) => { - try { - const response = await fetch(`/api/servers/${server.name}`, { - method: 'PUT', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(payload), - }); - - const result = await response.json(); - - if (!response.ok) { - alert(result.message || 'Failed to update server'); - return; - } - - onEdit(); - } catch (err) { - alert('Error: ' + err.message); - } - }; - - return ( -
- -
- ); -} - -// Root application component managing server state and UI -function App() { - const [servers, setServers] = useState([]); - const [error, setError] = useState(null); - const [refreshKey, setRefreshKey] = useState(0); - const [editingServer, setEditingServer] = useState(null); - - useEffect(() => { - fetch('/api/servers') - .then((response) => response.json()) - .then((data) => { - // 处理API响应中的包装对象,提取data字段 - if (data && data.success && Array.isArray(data.data)) { - setServers(data.data); - } else if (data && Array.isArray(data)) { - // 兼容性处理,如果API直接返回数组 - setServers(data); - } else { - // 如果数据格式不符合预期,设置为空数组 - console.error('Invalid server data format:', data); - setServers([]); - } - }) - .catch((err) => setError(err.message)); - - // Poll for updates every 5 seconds - const interval = setInterval(() => { - fetch('/api/servers') - .then((response) => response.json()) - .then((data) => { - // 处理API响应中的包装对象,提取data字段 - if (data && data.success && Array.isArray(data.data)) { - setServers(data.data); - } else if (data && Array.isArray(data)) { - // 兼容性处理,如果API直接返回数组 - setServers(data); - } else { - // 如果数据格式不符合预期,设置为空数组 - console.error('Invalid server data format:', data); - setServers([]); - } - }) - .catch((err) => setError(err.message)); - }, 5000); - - return () => clearInterval(interval); - }, [refreshKey]); - - const handleServerAdd = () => { - setRefreshKey((prevKey) => prevKey + 1); - }; - - const handleServerEdit = (server) => { - // Fetch settings to get the full server config before editing - fetch(`/api/settings`) - .then((response) => response.json()) - .then((settingsData) => { - if ( - settingsData && - settingsData.success && - settingsData.data && - settingsData.data.mcpServers && - settingsData.data.mcpServers[server.name] - ) { - const serverConfig = settingsData.data.mcpServers[server.name]; - const fullServerData = { - name: server.name, - status: server.status, - tools: server.tools || [], - config: serverConfig, - }; - - console.log('Editing server with config:', fullServerData); - setEditingServer(fullServerData); - } else { - console.error('Failed to get server config from settings:', settingsData); - setError(`Could not find configuration data for ${server.name}`); - } - }) - .catch((err) => { - console.error('Error fetching server settings:', err); - setError(err.message); - }); - }; - - const handleEditComplete = () => { - setEditingServer(null); - setRefreshKey((prevKey) => prevKey + 1); - }; - - const handleServerRemove = async (serverName) => { - try { - const response = await fetch(`/api/servers/${serverName}`, { - method: 'DELETE', - }); - const result = await response.json(); - - if (!response.ok) { - setError(result.message || `Failed to delete server ${serverName}`); - return; - } - - setRefreshKey((prevKey) => prevKey + 1); - } catch (err) { - setError('Error: ' + err.message); - } - }; - - if (error) { - return ( -
-
-
-

Error

-

{error}

- -
-
-
- ); - } - - return ( -
-
-
-

MCP Hub Dashboard

- -
- {servers.length === 0 ? ( -
-

No MCP servers available

-
- ) : ( -
- {servers.map((server, index) => ( - - ))} -
- )} - {editingServer && ( - setEditingServer(null)} - /> - )} -
-
- ); -} - -const root = ReactDOM.createRoot(document.getElementById('root')); -root.render(); diff --git a/src/middlewares/index.ts b/src/middlewares/index.ts index c7e8028..39e9378 100644 --- a/src/middlewares/index.ts +++ b/src/middlewares/index.ts @@ -15,7 +15,7 @@ export const errorHandler = ( }; export const initMiddlewares = (app: express.Application): void => { - app.use(express.static('public')); + app.use(express.static('frontend/dist')); app.use((req, res, next) => { if (req.path !== '/sse' && req.path !== '/messages') { @@ -26,7 +26,7 @@ export const initMiddlewares = (app: express.Application): void => { }); app.get('/', (_req: Request, res: Response) => { - res.sendFile(path.join(process.cwd(), 'public', 'index.html')); + res.sendFile(path.join(process.cwd(), 'frontend', 'dist', 'index.html')); }); app.use(errorHandler);