diff --git a/plugins/process-manager/.github/workflows/release.yml b/plugins/process-manager/.github/workflows/release.yml new file mode 100644 index 00000000..870f1b9d --- /dev/null +++ b/plugins/process-manager/.github/workflows/release.yml @@ -0,0 +1,36 @@ +name: Build and Release + +on: + workflow_dispatch: + +permissions: + contents: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + + - run: npm i + + - run: npm run build + + - name: Get package info + id: pkg + run: | + echo "name=$(node -p "require('./package.json').name")" >> "$GITHUB_OUTPUT" + echo "version=$(node -p "require('./package.json').version")" >> "$GITHUB_OUTPUT" + + - name: Zip dist + run: cd dist && zip -r ../${{ steps.pkg.outputs.name }}-v${{ steps.pkg.outputs.version }}.zip . && cd .. + + - name: Create Release + uses: softprops/action-gh-release@v2 + with: + tag_name: v${{ steps.pkg.outputs.version }} + files: ${{ steps.pkg.outputs.name }}-v${{ steps.pkg.outputs.version }}.zip diff --git a/plugins/process-manager/.gitignore b/plugins/process-manager/.gitignore new file mode 100644 index 00000000..2cbd7ded --- /dev/null +++ b/plugins/process-manager/.gitignore @@ -0,0 +1,32 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# vibe coding +.agents +.reasonix +reasonix* +.opencode +skills-lock.json + diff --git a/plugins/process-manager/CHANGELOG.md b/plugins/process-manager/CHANGELOG.md new file mode 100644 index 00000000..f09ccc97 --- /dev/null +++ b/plugins/process-manager/CHANGELOG.md @@ -0,0 +1,20 @@ +# Changelog + +## [1.0.0] — 2026-07-07 + +### 🎉 初始发布 + +Process Port Manager 正式发布 — 为 ZTools 打造的进程端口管理插件,支持快速搜索进程、端口、PID、路径,并一键 Kill 进程。 + +### ✨ 功能特性 + +- **🔍 多维搜索** — 支持按进程名、PID、端口号、文件路径模糊搜索,一处输入全字段匹配 +- **📋 一键复制** — 点击进程名 / PID / 端口号 / 路径即可复制到剪贴板 +- **⚡ 一键 Kill 进程** — 通过右键唤出确认弹窗,确认后调用 `taskkill /F` 强制终止进程 +- **🔄 自动刷新** — 5 秒缓存 TTL,进度条直观展示缓存倒计时 +- **🌙 暗色模式** — 跟随系统主题自适应,UI 与 ZTools 原生风格统一 +- **🪟 Windows 原生** — 基于 `wmic` / `netstat` / `tasklist`,零第三方系统依赖 + +### 📄 许可 + +MIT License diff --git a/plugins/process-manager/LICENSE b/plugins/process-manager/LICENSE new file mode 100644 index 00000000..2339c405 --- /dev/null +++ b/plugins/process-manager/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 lvlizhe + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/plugins/process-manager/README.md b/plugins/process-manager/README.md new file mode 100644 index 00000000..1aa91281 --- /dev/null +++ b/plugins/process-manager/README.md @@ -0,0 +1,202 @@ +

+ Process Port Manager Logo +

+ +

进程端口管理

+

Process Port Manager — 为 ZTools 打造的进程端口管理插件

+ +![Platform](https://img.shields.io/badge/platform-win32-blue) +![React](https://img.shields.io/badge/React-19-61DAFB) +![Vite](https://img.shields.io/badge/Vite-6-646CFF) +![License](https://img.shields.io/badge/license-MIT-green) + +> 🤖 **本项目由 [Vibe Coding](https://github.com/nicepkg/vibe-coding) 维护** — AI 辅助驱动的开发模式,让想法快速变为代码。 + +--- + +## 💡 为什么选择 Process Port Manager? + +- **UI 和谐统一** — 背景、字体、配色沿用 ZTools 本身,如同原生功能,无视觉割裂感 +- **信息简洁清晰** — 只展示进程名、PID、端口、路径等关键信息,点击任意字段即可一键复制 +- **开放源代码** — MIT 协议开源,可自由修改、扩展、二次开发 + +--- + + +## 📸 界面预览 +### 暗黑 + + + +### 明亮 + + + +## ✨ 功能特性 + +- **🔍 多维搜索** — 支持按进程名、PID、端口号、文件路径模糊搜索,一处输入全字段匹配 +- **📋 一键复制** — 点击进程名 / PID / 端口号 / 路径即可复制到剪贴板 +- **⚡ 一键 Kill** — 确认后通过 `taskkill /F` 强制终止进程 +- **🔄 自动刷新** — 5 秒缓存 TTL,进度条直观展示缓存倒计时 +- **🌙 暗色模式** — 跟随系统主题自适应 +- **🪟 Windows 原生** — 基于 `wmic` / `netstat` / `tasklist`,无第三方依赖 + + +## 🚀 快速开始 + +### 安装依赖 + +```bash +npm install +``` + +### 开发模式 + +```bash +npm run dev +``` + +开发服务器将在 `http://localhost:5173` 启动。ZTools 会自动加载开发版本。 + +### 构建生产版本 + +```bash +npm run build +``` + +构建产物将输出到 `dist/` 目录。 + +## 📁 项目结构 + +``` +. +├── public/ +│ ├── logo.svg # 插件图标(SVG) +│ ├── plugin.json # 插件配置(功能指令、平台等) +│ └── preload/ +│ └── services.js # Node.js 层:进程枚举、端口扫描、Kill +├── src/ +│ ├── main.tsx # 入口文件 +│ ├── main.css # 全局样式(含暗色模式) +│ ├── App.tsx # 根组件(路由分发) +│ ├── env.d.ts # 类型声明(ztools / services) +│ └── ProcessManager/ +│ ├── index.tsx # 主界面组件 +│ ├── index.css # 组件样式 +│ ├── search.ts # 多功能搜索(进程名/PID/端口/路径) +│ └── useProcessData.ts # 进程数据获取 + 缓存 + 定时刷新 +├── index.html +├── vite.config.js +├── tsconfig.json +├── package.json +└── README.md +``` + +## 🧠 核心逻辑 + +### 数据流 + +``` +┌─────────────────────┐ +│ services.js │ ← Node.js preload +│ ─ listProcesses() │ wmic / tasklist +│ ─ scanPorts() │ netstat -ano +│ ─ killProcess() │ taskkill /F +└────────┬────────────┘ + │ window.services.* + ▼ +┌─────────────────────┐ +│ useProcessData.ts │ ← 5s 缓存 + 防抖刷新 +│ ─ processes[] │ +│ ─ refresh() │ +└────────┬────────────┘ + ▼ +┌─────────────────────┐ +│ search.ts │ ← 按 PID/端口/进程名/路径 过滤 +│ ─ searchProcesses()│ +└────────┬────────────┘ + ▼ +┌─────────────────────┐ +│ ProcessManager │ ← React 组件渲染 +│ → 进程列表 + Kill │ +└─────────────────────┘ +``` + +### 主要依赖 + +| 模块 | 职责 | +| -------------------------- | --------------------------------------------------------------------- | +| `services.js` | 通过 `wmic` 枚举进程、`netstat -ano` 扫描端口、`taskkill /F` 终止进程 | +| `useProcessData.ts` | 管理进程数据状态,5 秒缓存 TTL,自动/手动刷新 | +| `search.ts` | 多字段模糊匹配:进程名、PID、端口、路径 | +| `ProcessManager/index.tsx` | 主界面:搜索栏、结果列表、Kill 按钮、Toast 提示 | + +## 🔧 插件配置 + +编辑 `public/plugin.json`: + +```json +{ + "name": "process-manager", + "title": "进程端口管理", + "description": "快速搜索进程、端口、PID、路径,支持一键 Kill 进程,支持深色模式", + "features": [ + { + "code": "process", + "explain": "快速搜索进程、端口、PID,支持一键 Kill", + "cmds": ["pid", "port", "process", "端口", "进程", "进程管理"] + } + ], + "platform": ["win32"] +} +``` + +触发指令:`pid` / `port` / `process` / `端口` / `进程` / `进程管理` + +> ⚠ 当前仅支持 Windows 平台(依赖 `wmic`、`netstat`、`taskkill`)。 + +## 📦 构建与发布 + +```bash +npm run build # 构建 +``` + +将 `dist/` 目录中的所有文件复制到 ZTools 插件目录即可使用。 + +## 🎨 样式开发 + +组件使用 CSS 变量实现主题适配与暗色模式: + +```css +/* 跟随 ZTools 主题 */ +.pm { + background: var(--bg-color); + color: var(--text-color); +} + +/* 暗色模式自适应 */ +@media (prefers-color-scheme: dark) { + .pm-item { + background: #2a2a2a; + } +} +``` + +## 🤖 Vibe Coding 说明 + +本项目采用 **Vibe Coding** 理念进行维护: + +- **AI 优先** — 需求描述直接转化为可用代码,快速迭代 +- **最小阻力** — 减少手动配置,聚焦核心功能 +- **持续优化** — 在 AI 辅助下不断重构和完善 + +欢迎通过 Issue 或 PR 贡献想法和改进! + + +## 📄 开源协议 + +MIT License + +--- + +**优化建议或问题?欢迎提出 Issue!** 🎉 diff --git a/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-design.md b/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-design.md new file mode 100644 index 00000000..0bb2ad9b --- /dev/null +++ b/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-design.md @@ -0,0 +1,167 @@ +# Process Port Manager — Design Spec + +## Overview + +A ZTools plugin (utools-like launcher) for searching and killing Windows processes by PID, port number, process name, or file path. Built with React 18 + Vite + TypeScript, following the existing plugin template patterns. + +## Architecture + +**Approach: React component + preload services backend** + +``` +preload/services.js src/ProcessManager/ + listProcesses() index.tsx (UI component) + scanPorts() index.css + killProcess(pid) useProcessData.ts (collection + cache hook) + search.ts (smart matching engine) +``` + +- `services.js` — thin system-command layer, exposes 3 async methods +- React component — state management, search filtering, cache control, rendering +- `search.ts` — pure function: `(keyword, processes) => filtered[]` with auto-detect matching + +## Data Flow + +``` +User types "8080" + │ + ▼ +search.ts smart matching + │ auto-detects: number → match PID + Port + ▼ +Filter in cached process list + │ + ├─ click Kill ──► services.killProcess(pid) ──► toast result + │ + └─ continue typing ──► real-time filter (cache only, no re-collect) +``` + +**Cache strategy:** +- On feature enter: call `listProcesses()` + `scanPorts()` once +- Merge data by PID into `ProcessInfo[]` +- Cache for 5 seconds +- Within 5s: search filters cache only +- After 5s: auto-refresh on next keystroke + +## Preload Services API + +```ts +interface ProcessRaw { pid: number; name: string; path: string; status: string } +interface PortEntry { port: number; pid: number; protocol: string } + +services.listProcesses(): Promise +services.scanPorts(): Promise +services.killProcess(pid: number): Promise<{ success: boolean; error?: string }> +``` + +**Windows implementation:** +- `listProcesses` → `tasklist /FO CSV /NH` +- `scanPorts` → `netstat -ano` parse all TCP/UDP (listening + established) +- `killProcess` → `taskkill /PID /F`, catch error on permission failure + +## Smart Matching Engine + +Pure function with no side effects. Matching priority: + +| Input | Match behavior | +|---|---| +| `8080` (numeric) | Match PID and Port | +| `chrome` (alpha) | Match process name (case-insensitive) | +| `nginx.exe` (has `.exe`) | Match name + path | +| `C:\Users` (has path sep) | Match file path | +| `443 8080` (multiple nums) | Match multiple ports/PIDs | + +Sorting: exact match > prefix match > fuzzy contains. + +## Core Types + +```ts +interface ProcessInfo { + pid: number + name: string + path: string + ports: number[] +} +``` + +## UI Layout + +``` +┌─────────────────────────────────────┐ +│ 🔍 搜索进程... │ +│ [进程名] [PID] [路径...] [✕] │ +│ 🔌 8080 🔌 443 🔌 3000 │ +├─────────────────────────────────────┤ +│ [chrome.exe] [1234] 路径... [✕] │ +│ 🔌 8080 🔌 443 │ +├─────────────────────────────────────┤ +│ [node.exe] [5678] 路径... [✕] │ +│ 🔌 9229 │ +├─────────────────────────────────────┤ +│ 共3个结果 5s后自动刷新 │ +└─────────────────────────────────────┘ +``` + +- **Row 1**: `[name tag] [PID tag] [path...]` + Kill `✕` button right-aligned, vertically centered +- **Row 2**: Port tags `🔌 port` +- Every info element is clickable to copy its content (toast: "已复制: xxx") +- Search auto-focuses, real-time filtering + +## Interaction + +- Enter via ZTools command trigger (keywords: `pid`, `port`, `process`, `端口`, `进程`, `进程管理`) +- Kill button click → confirmation dialog → `services.killProcess(pid)` → success/error toast +- Permission errors shown as toast, not thrown + +## Theme / Color Scheme + +### Light — Blue-Purple (default) +| Element | Background | Text | +|---|---|---| +| Process name tag | `#e8eaf6` | `#283593` | +| PID tag | `#f3e8ff` | `#7c3aed` | +| Port tag | `#eef2ff` | `#4f6ef7` | +| Path | transparent | `#b0b0c0` | +| Search box | `#f0f2f5` | `#333` | +| Kill button | `#fee2e2` | `#ef4444` | +| Kill hover | `#ef4444` | `#fff` | + +### Dark — Terminal Green +| Element | Background | Text | +|---|---|---| +| Background | `#0d1117` | — | +| Process name tag | `#1a3a2a` | `#7ee787` | +| PID tag | `#3a2a1a` | `#d29922` | +| Port tag | `#1a2a3a` | `#58a6ff` | +| Path | transparent | `#8b949e` | +| Search box | `#21262d` | `#c9d1d9` | +| Kill button | `#3a1a1a` | `#fca5a5` | +| Kill hover | `#ef4444` | `#fff` | + +## Theme Switching + +Theme follows the OS-level `prefers-color-scheme` media query. No manual toggle. When the user switches their OS between light/dark mode, the plugin adapts automatically via CSS media queries. + +Implementation: define two CSS classless theme blocks scoped by `@media (prefers-color-scheme: light/dark)`. + +## YAGNI Exclusions + +- No CPU/memory usage display (not needed for port/process search) +- No process status tag (removed as not meaningful) +- No continuous polling — cached 5s refresh only +- No batch kill — single process kill only +- No remote process management — local Windows only + +## Files to Create + +### New files +- `src/ProcessManager/index.tsx` — main component +- `src/ProcessManager/index.css` — styles +- `src/ProcessManager/search.ts` — matching engine +- `src/ProcessManager/useProcessData.ts` — data hook + +### Modified files +- `public/plugin.json` — register new feature with cmds: `pid`, `port`, `process`, `端口`, `进程`, `进程管理` +- `public/preload/services.js` — add listProcesses, scanPorts, killProcess +- `src/App.tsx` — add ProcessManager route +- `src/env.d.ts` — add types for new services diff --git a/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-plan.md b/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-plan.md new file mode 100644 index 00000000..f7aa6cd7 --- /dev/null +++ b/plugins/process-manager/docs/superpowers/specs/2026-07-06-process-manager-plan.md @@ -0,0 +1,45 @@ +# Process Port Manager — Implementation Plan + +## Step 1: preload services (`public/preload/services.js`) +- Add `listProcesses()` using `tasklist /FO CSV /NH` (Windows) +- Add `scanPorts()` using `netstat -ano` parsing +- Add `killProcess(pid)` using `taskkill /PID /F` +- Wrap all in try/catch, return structured results + +## Step 2: type declarations (`src/env.d.ts`) +- Add `ProcessRaw`, `PortEntry`, `ProcessInfo` interfaces +- Extend `Window.services` with new methods + +## Step 3: plugin config (`public/plugin.json`) +- Register new feature `code: "process"` with cmds: `pid`, `port`, `process`, `端口`, `进程`, `进程管理` + +## Step 4: smart matching engine (`src/ProcessManager/search.ts`) +- Pure function `searchProcesses(keyword, list): ProcessInfo[]` +- Numeric detection → match PID + Port +- Alpha detection → match name (case-insensitive) +- Path detection → match path +- Multiple values → OR match +- Sort: exact > prefix > fuzzy + +## Step 5: data hook (`src/ProcessManager/useProcessData.ts`) +- On mount: call `services.listProcesses()` + `services.scanPorts()` +- Merge by PID into `ProcessInfo[]` +- Cache with 5s TTL +- Expose `processes`, `loading`, `refresh`, `lastFetched` + +## Step 6: UI component (`src/ProcessManager/index.tsx` + `index.css`) +- Search input (auto-focus, controlled) +- Result list with items showing: [name tag] [PID tag] [path...] + Kill button +- Port tags on second row +- Click-to-copy on each info element +- Kill confirmation → call services.killProcess +- Theme light/dark via `prefers-color-scheme` +- Footer with count + refresh timer + +## Step 7: routing (`src/App.tsx`) +- Import ProcessManager +- Add route `process` → ProcessManager + +## Step 8: verify +- `npm run build` passes +- UI matches mockups diff --git a/plugins/process-manager/index.html b/plugins/process-manager/index.html new file mode 100644 index 00000000..84b7eb68 --- /dev/null +++ b/plugins/process-manager/index.html @@ -0,0 +1,11 @@ + + + + + + + +
+ + + diff --git a/plugins/process-manager/package.json b/plugins/process-manager/package.json new file mode 100644 index 00000000..8327b646 --- /dev/null +++ b/plugins/process-manager/package.json @@ -0,0 +1,23 @@ +{ + "name": "process-manager", + "version": "1.0.0", + "description": "", + "type": "module", + "scripts": { + "dev": "vite", + "build": "tsc && vite build" + }, + "dependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0", + "react-icons": "^5.7.0" + }, + "devDependencies": { + "@types/react": "^19.0.8", + "@types/react-dom": "^19.0.3", + "@vitejs/plugin-react": "^4.3.1", + "@ztools-center/ztools-api-types": "^1.0.1", + "typescript": "^5.3.0", + "vite": "^6.0.11" + } +} diff --git a/plugins/process-manager/pnpm-lock.yaml b/plugins/process-manager/pnpm-lock.yaml new file mode 100644 index 00000000..5818c351 --- /dev/null +++ b/plugins/process-manager/pnpm-lock.yaml @@ -0,0 +1,1154 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + react: + specifier: ^19.0.0 + version: 19.2.7 + react-dom: + specifier: ^19.0.0 + version: 19.2.7(react@19.2.7) + react-icons: + specifier: ^5.7.0 + version: 5.7.0(react@19.2.7) + devDependencies: + '@types/react': + specifier: ^19.0.8 + version: 19.2.17 + '@types/react-dom': + specifier: ^19.0.3 + version: 19.2.3(@types/react@19.2.17) + '@vitejs/plugin-react': + specifier: ^4.3.1 + version: 4.7.0(vite@6.4.3) + '@ztools-center/ztools-api-types': + specifier: ^1.0.1 + version: 1.0.3 + typescript: + specifier: ^5.3.0 + version: 5.9.3 + vite: + specifier: ^6.0.11 + version: 6.4.3 + +packages: + + '@babel/code-frame@7.29.7': + resolution: {integrity: sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.7': + resolution: {integrity: sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.7': + resolution: {integrity: sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.7': + resolution: {integrity: sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.29.7': + resolution: {integrity: sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.29.7': + resolution: {integrity: sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.29.7': + resolution: {integrity: sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.29.7': + resolution: {integrity: sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.29.7': + resolution: {integrity: sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.29.7': + resolution: {integrity: sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.29.7': + resolution: {integrity: sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.29.7': + resolution: {integrity: sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.29.7': + resolution: {integrity: sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.29.7': + resolution: {integrity: sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.7': + resolution: {integrity: sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/rollup-android-arm-eabi@4.62.2': + resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.62.2': + resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.62.2': + resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.62.2': + resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.62.2': + resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.62.2': + resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.62.2': + resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.62.2': + resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.62.2': + resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.62.2': + resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.62.2': + resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.62.2': + resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.62.2': + resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.62.2': + resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==} + cpu: [x64] + os: [win32] + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/estree@1.0.9': + resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.17': + resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@ztools-center/ztools-api-types@1.0.3': + resolution: {integrity: sha512-dv1eOAIasAupqKaQL/gESk1i2+RebdM/1gvZhrvH2D/bo3enCUsAGJ8nrHnlCLBSOGB81eC/SU0IH8BNsUlmvA==} + + baseline-browser-mapping@2.10.42: + resolution: {integrity: sha512-c/jurFrDLyui7o1J86yLkRu4LMsTYcBohveus7/I2Hzdn9KIP2bdJPTue/lR1KH46enoPbD77GKeSYNdyPoD3Q==} + engines: {node: '>=6.0.0'} + hasBin: true + + browserslist@4.28.5: + resolution: {integrity: sha512-Cu2E6QejHWzuDMTkuwgpABFgDfZrXLQq5V13YOACZx4mFAG4IwGTbTfHPMr4WtxlHoXSM8FIuRwYYCz5XiabaQ==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + caniuse-lite@1.0.30001803: + resolution: {integrity: sha512-g/uHREV2ZpK9qMalCsWaxmA6ol+DX8GYhuf3T40RKoP+oL7vhRJh8LNt73PCjpnR6l14FzfPrB5Yux4PKm2meg==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + electron-to-chromium@1.5.388: + resolution: {integrity: sha512-Pl/aJaqOOxYxda3vcx1IKSJimwYXHDkEnGn0F+kG2EE68dDtx2uCinaS+Vih8Z91B9t8CSAbiF/HKyWcnXjhzw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.15: + resolution: {integrity: sha512-y7Wygv/7mEOvxTuEQDB8StXdMRBWf1kR/tlhAzBRUFkB2jfcLOAxO/SHmOO2zgz1pVgK29/kyupn059/bCHdjA==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} + engines: {node: '>=18'} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} + engines: {node: '>=12'} + + postcss@8.5.16: + resolution: {integrity: sha512-vuwillviilfKZsg0VGj5R/YwwcHx4SLsIOI/7K6mQkWx+l5cUHTjj5g0AasTBcyXsbfTgrwsUNmVUb5xVwyPwg==} + engines: {node: ^10 || ^12 || >=14} + + react-dom@19.2.7: + resolution: {integrity: sha512-t0BRVXvbiE/o20Hfw669rLbMCDWtYZLvmJigy2f0MxsXF+71pxhR3xOkspmsO8h3ZlNzyibAmtCa3l4lYKk6gQ==} + peerDependencies: + react: ^19.2.7 + + react-icons@5.7.0: + resolution: {integrity: sha512-LBLy340Rzqy6+/yVhZKT3B/QpP1BZaesGqasf09HPOBzRarcDIFH0WwXlXQfE7q7ipxK4MSiC5DIBWURCny6fw==} + peerDependencies: + react: '*' + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.7: + resolution: {integrity: sha512-HNe9WslTbXmFK8o8cmwgAeJFSBvt1bPdHCVKtaaV+WlAN36mpT4hcRpwbf3fY56ar2oIXzsBpOAiIRHAdY0OlQ==} + engines: {node: '>=0.10.0'} + + rollup@4.62.2: + resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + vite@6.4.3: + resolution: {integrity: sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + +snapshots: + + '@babel/code-frame@7.29.7': + dependencies: + '@babel/helper-validator-identifier': 7.29.7 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.7': {} + + '@babel/core@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-compilation-targets': 7.29.7 + '@babel/helper-module-transforms': 7.29.7(@babel/core@7.29.7) + '@babel/helpers': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.7': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.29.7': + dependencies: + '@babel/compat-data': 7.29.7 + '@babel/helper-validator-option': 7.29.7 + browserslist: 4.28.5 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.29.7': {} + + '@babel/helper-module-imports@7.29.7': + dependencies: + '@babel/traverse': 7.29.7 + '@babel/types': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-module-imports': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + '@babel/traverse': 7.29.7 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.29.7': {} + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-option@7.29.7': {} + + '@babel/helpers@7.29.7': + dependencies: + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/plugin-transform-react-jsx-self@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/plugin-transform-react-jsx-source@7.29.7(@babel/core@7.29.7)': + dependencies: + '@babel/core': 7.29.7 + '@babel/helper-plugin-utils': 7.29.7 + + '@babel/template@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@babel/traverse@7.29.7': + dependencies: + '@babel/code-frame': 7.29.7 + '@babel/generator': 7.29.7 + '@babel/helper-globals': 7.29.7 + '@babel/parser': 7.29.7 + '@babel/template': 7.29.7 + '@babel/types': 7.29.7 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/rollup-android-arm-eabi@4.62.2': + optional: true + + '@rollup/rollup-android-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-arm64@4.62.2': + optional: true + + '@rollup/rollup-darwin-x64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-arm64@4.62.2': + optional: true + + '@rollup/rollup-freebsd-x64@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.62.2': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-linux-x64-musl@4.62.2': + optional: true + + '@rollup/rollup-openbsd-x64@4.62.2': + optional: true + + '@rollup/rollup-openharmony-arm64@4.62.2': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.62.2': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.62.2': + optional: true + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.29.7 + + '@types/estree@1.0.9': {} + + '@types/react-dom@19.2.3(@types/react@19.2.17)': + dependencies: + '@types/react': 19.2.17 + + '@types/react@19.2.17': + dependencies: + csstype: 3.2.3 + + '@vitejs/plugin-react@4.7.0(vite@6.4.3)': + dependencies: + '@babel/core': 7.29.7 + '@babel/plugin-transform-react-jsx-self': 7.29.7(@babel/core@7.29.7) + '@babel/plugin-transform-react-jsx-source': 7.29.7(@babel/core@7.29.7) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.3 + transitivePeerDependencies: + - supports-color + + '@ztools-center/ztools-api-types@1.0.3': {} + + baseline-browser-mapping@2.10.42: {} + + browserslist@4.28.5: + dependencies: + baseline-browser-mapping: 2.10.42 + caniuse-lite: 1.0.30001803 + electron-to-chromium: 1.5.388 + node-releases: 2.0.50 + update-browserslist-db: 1.2.3(browserslist@4.28.5) + + caniuse-lite@1.0.30001803: {} + + convert-source-map@2.0.0: {} + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + electron-to-chromium@1.5.388: {} + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + escalade@3.2.0: {} + + fdir@6.5.0(picomatch@4.0.5): + optionalDependencies: + picomatch: 4.0.5 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + js-tokens@4.0.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + ms@2.1.3: {} + + nanoid@3.3.15: {} + + node-releases@2.0.50: {} + + picocolors@1.1.1: {} + + picomatch@4.0.5: {} + + postcss@8.5.16: + dependencies: + nanoid: 3.3.15 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + react-dom@19.2.7(react@19.2.7): + dependencies: + react: 19.2.7 + scheduler: 0.27.0 + + react-icons@5.7.0(react@19.2.7): + dependencies: + react: 19.2.7 + + react-refresh@0.17.0: {} + + react@19.2.7: {} + + rollup@4.62.2: + dependencies: + '@types/estree': 1.0.9 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.62.2 + '@rollup/rollup-android-arm64': 4.62.2 + '@rollup/rollup-darwin-arm64': 4.62.2 + '@rollup/rollup-darwin-x64': 4.62.2 + '@rollup/rollup-freebsd-arm64': 4.62.2 + '@rollup/rollup-freebsd-x64': 4.62.2 + '@rollup/rollup-linux-arm-gnueabihf': 4.62.2 + '@rollup/rollup-linux-arm-musleabihf': 4.62.2 + '@rollup/rollup-linux-arm64-gnu': 4.62.2 + '@rollup/rollup-linux-arm64-musl': 4.62.2 + '@rollup/rollup-linux-loong64-gnu': 4.62.2 + '@rollup/rollup-linux-loong64-musl': 4.62.2 + '@rollup/rollup-linux-ppc64-gnu': 4.62.2 + '@rollup/rollup-linux-ppc64-musl': 4.62.2 + '@rollup/rollup-linux-riscv64-gnu': 4.62.2 + '@rollup/rollup-linux-riscv64-musl': 4.62.2 + '@rollup/rollup-linux-s390x-gnu': 4.62.2 + '@rollup/rollup-linux-x64-gnu': 4.62.2 + '@rollup/rollup-linux-x64-musl': 4.62.2 + '@rollup/rollup-openbsd-x64': 4.62.2 + '@rollup/rollup-openharmony-arm64': 4.62.2 + '@rollup/rollup-win32-arm64-msvc': 4.62.2 + '@rollup/rollup-win32-ia32-msvc': 4.62.2 + '@rollup/rollup-win32-x64-gnu': 4.62.2 + '@rollup/rollup-win32-x64-msvc': 4.62.2 + fsevents: 2.3.3 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + source-map-js@1.2.1: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + + typescript@5.9.3: {} + + update-browserslist-db@1.2.3(browserslist@4.28.5): + dependencies: + browserslist: 4.28.5 + escalade: 3.2.0 + picocolors: 1.1.1 + + vite@6.4.3: + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 + postcss: 8.5.16 + rollup: 4.62.2 + tinyglobby: 0.2.17 + optionalDependencies: + fsevents: 2.3.3 + + yallist@3.1.1: {} diff --git a/plugins/process-manager/public/logo.svg b/plugins/process-manager/public/logo.svg new file mode 100644 index 00000000..7300af33 --- /dev/null +++ b/plugins/process-manager/public/logo.svg @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/plugins/process-manager/public/plugin.json b/plugins/process-manager/public/plugin.json new file mode 100644 index 00000000..2a5b3ece --- /dev/null +++ b/plugins/process-manager/public/plugin.json @@ -0,0 +1,33 @@ +{ + "$schema": "node_modules/@ztools-center/ztools-api-types/resource/ztools.schema.json", + "name": "process-manager", + "title": "进程端口管理", + "description": "快速搜索进程、端口、PID、路径,支持一键 Kill 进程,支持深色模式", + "author": "Agitator", + "version": "1.0.0", + "homepage": "https://github.com/Code-Agitator/process-manager", + "main": "index.html", + "preload": "preload/services.js", + "logo": "logo.svg", + "development": { + "main": "http://localhost:5173" + }, + "features": [ + { + "code": "process", + "explain": "快速搜索进程、端口、PID,支持一键 Kill", + "icon": "logo.svg", + "cmds": [ + "pid", + "port", + "process", + "端口", + "进程", + "进程管理" + ] + } + ], + "platform": [ + "win32" + ] +} \ No newline at end of file diff --git a/plugins/process-manager/public/preload/package.json b/plugins/process-manager/public/preload/package.json new file mode 100644 index 00000000..5bbefffb --- /dev/null +++ b/plugins/process-manager/public/preload/package.json @@ -0,0 +1,3 @@ +{ + "type": "commonjs" +} diff --git a/plugins/process-manager/public/preload/services.js b/plugins/process-manager/public/preload/services.js new file mode 100644 index 00000000..4eae12cb --- /dev/null +++ b/plugins/process-manager/public/preload/services.js @@ -0,0 +1,99 @@ +const { exec, spawn } = require('node:child_process') + +function execAsync(cmd, timeout) { + return new Promise((resolve) => { + exec(cmd, { timeout }, (err, stdout) => { + resolve(err ? '' : stdout) + }) + }) +} + +function parseWmicCsv(output) { + const lines = output.trim().replace(/\r/g, '').split('\n').filter(Boolean) + if (lines.length < 2) return [] + const header = lines[0].split(',').map(h => h.trim().toLowerCase()) + const nameIdx = header.indexOf('name') + const pidIdx = header.indexOf('processid') + const pathIdx = header.indexOf('executablepath') + if (nameIdx === -1 || pidIdx === -1) return [] + + return lines.slice(1).map(line => { + const parts = line.startsWith('"') + ? line.replace(/^"|"$/g, '').split('","') + : line.split(',') + return { + name: (parts[nameIdx] || '').trim(), + pid: parseInt(parts[pidIdx], 10) || 0, + path: (parts[pathIdx] || '').trim() + } + }).filter(p => p.pid > 0) +} + +function parseNetstat(output) { + const lines = output.trim().replace(/\r/g, '').split('\n').filter(Boolean) + const ports = [] + for (const line of lines) { + const parts = line.trim().split(/\s+/) + if (parts.length < 5) continue + const addr = parts[1] + const pid = parseInt(parts[4], 10) + if (!addr || isNaN(pid)) continue + const portMatch = addr.match(/:(\d+)$/) + if (portMatch) { + ports.push({ port: parseInt(portMatch[1], 10), pid, protocol: parts[0] }) + } + } + return ports +} + +window.services = { + async listProcesses() { + let raw = await execAsync('wmic process get ProcessId,Name,ExecutablePath /FORMAT:CSV', 8000) + if (raw) return parseWmicCsv(raw) + raw = await execAsync('tasklist /FO CSV /NH', 5000) + if (!raw) return [] + const lines = raw.trim().replace(/\r/g, '').split('\n').filter(Boolean) + return lines.map(line => { + const parts = line.replace(/^"|"$/g, '').split('","') + return { name: parts[0] || '', pid: parseInt(parts[1], 10) || 0, path: '' } + }).filter(p => p.pid > 0) + }, + + async scanPorts() { + const raw = await execAsync('netstat -ano', 5000) + return raw ? parseNetstat(raw) : [] + }, + + async killProcess(pid) { + // 严格校验 pid 为正整数 + if (!Number.isInteger(pid) || pid <= 0) { + return { success: false, error: '无效的 PID' } + } + + return new Promise((resolve) => { + const child = spawn('taskkill', ['/PID', String(pid), '/F'], { + timeout: 3000, + stdio: ['ignore', 'pipe', 'pipe'] + }) + + let stderr = '' + child.stderr.on('data', (data) => { + stderr += data.toString() + }) + + child.on('close', (code) => { + if (code === 0) { + resolve({ success: true }) + } else { + console.error('Kill process failed:', stderr) + resolve({ success: false, error: 'Kill 失败' }) + } + }) + + child.on('error', (err) => { + console.error('Kill process error:', err) + resolve({ success: false, error: 'Kill 失败' }) + }) + }) + } +} diff --git a/plugins/process-manager/snapshot/image1.png b/plugins/process-manager/snapshot/image1.png new file mode 100644 index 00000000..199a0f93 Binary files /dev/null and b/plugins/process-manager/snapshot/image1.png differ diff --git a/plugins/process-manager/snapshot/image2.png b/plugins/process-manager/snapshot/image2.png new file mode 100644 index 00000000..f83839fb Binary files /dev/null and b/plugins/process-manager/snapshot/image2.png differ diff --git a/plugins/process-manager/snapshot/image3.png b/plugins/process-manager/snapshot/image3.png new file mode 100644 index 00000000..b192c526 Binary files /dev/null and b/plugins/process-manager/snapshot/image3.png differ diff --git a/plugins/process-manager/snapshot/image4.png b/plugins/process-manager/snapshot/image4.png new file mode 100644 index 00000000..5ac2742b Binary files /dev/null and b/plugins/process-manager/snapshot/image4.png differ diff --git a/plugins/process-manager/src/App.tsx b/plugins/process-manager/src/App.tsx new file mode 100644 index 00000000..cb8aaec6 --- /dev/null +++ b/plugins/process-manager/src/App.tsx @@ -0,0 +1,28 @@ +import { useEffect, useState } from 'react' +import ProcessManager from './ProcessManager' + +export default function App() { + const [_, setRoute] = useState('') + const [keyword, setKeyword] = useState('') + + useEffect(() => { + window.ztools.onPluginEnter((action) => { + setRoute(action.code) + if (action.code === 'process') { + setKeyword('') + window.ztools.setSubInput( + (input) => setKeyword(input.text), + '输入 PID / 端口 / 进程名 / 路径搜索...' + ) + } + }) + window.ztools.onPluginOut(() => { + setRoute('') + setKeyword('') + window.ztools.removeSubInput() + }) + }, []) + + return + +} diff --git a/plugins/process-manager/src/ProcessManager/index.css b/plugins/process-manager/src/ProcessManager/index.css new file mode 100644 index 00000000..3ae1b085 --- /dev/null +++ b/plugins/process-manager/src/ProcessManager/index.css @@ -0,0 +1,448 @@ +.pm { + display: flex; + flex-direction: column; + height: 100vh; + overflow: hidden; + background: transparent; + color: #333; + font-size: 13px; +} + +.pm-header { + padding: 10px 16px; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + flex-shrink: 0; + display: flex; + align-items: center; + justify-content: space-between; + gap: 8px; +} + +.pm-hints { + display: flex; + gap: 4px; + flex-wrap: wrap; +} + +.pm-hint { + font-size: 10px; + padding: 1px 7px; + border-radius: 4px; + font-weight: 500; +} + +.pm-hint-name { background: #e8eaf6; color: #283593; } +.pm-hint-pid { background: #f3e8ff; color: #7c3aed; } +.pm-hint-port { background: #eef2ff; color: #4f6ef7; } +.pm-hint-path { background: rgba(0, 0, 0, 0.06); color: #888; } + +.pm-body { + flex: 1; + overflow-y: auto; + padding: 4px 0; + width: 100%; + scrollbar-width: thin; + scrollbar-color: rgba(0, 0, 0, 0.2) rgba(0, 0, 0, 0.05); +} + +.pm-body::-webkit-scrollbar { + width: 6px; +} + +.pm-body::-webkit-scrollbar-track { + background: rgba(0, 0, 0, 0.05); +} + +.pm-body::-webkit-scrollbar-thumb { + background: rgba(0, 0, 0, 0.2); + border-radius: 3px; +} + +.pm-body::-webkit-scrollbar-thumb:hover { + background: rgba(0, 0, 0, 0.35); +} + +.pm-empty { + text-align: center; + padding: 40px 16px; + color: #b0b0c0; + font-size: 13px; +} + +.pm-item { + padding: 12px 0; + border-bottom: 1px solid rgba(0, 0, 0, 0.08); + transition: background 0.15s; +} + +.pm-item:hover { + background: rgba(0, 0, 0, 0.03); +} + +.pm-item:last-child { + border-bottom: none; +} + +.pm-item-top { + display: flex; + justify-content: space-between; + align-items: center; + gap: 10px; + padding: 0 16px; +} + +.pm-item-info { + flex: 1; + min-width: 0; +} + +.pm-item-row1 { + display: flex; + align-items: center; + gap: 5px; +} + +.pm-tag { + display: inline-block; + font-size: 10px; + padding: 2px 7px; + border-radius: 4px; + font-weight: 500; + white-space: nowrap; + cursor: pointer; + flex-shrink: 0; + transition: opacity 0.15s; +} + +.pm-tag:active { + opacity: 0.5; +} + +.pm-tag-name { background: #e8eaf6; color: #283593; } +.pm-tag-pid { background: #f3e8ff; color: #7c3aed; } +.pm-tag-port { background: #eef2ff; color: #4f6ef7; } +.pm-tag-none { background: rgba(0, 0, 0, 0.06); color: #999; cursor: default; } + +.pm-path { + font-size: 11px; + color: #b0b0c0; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + flex: 1; + min-width: 0; + cursor: pointer; + padding: 2px 4px; + border-radius: 4px; + transition: background 0.15s; +} + +.pm-path:hover { + background: rgba(0, 0, 0, 0.05); +} + +.pm-tags { + display: flex; + gap: 3px; + flex-wrap: wrap; + margin-top: 5px; +} + +.pm-footer { + display: flex; + justify-content: space-between; + align-items: center; + padding: 10px 16px; + border-top: 1px solid rgba(0, 0, 0, 0.08); + font-size: 11px; + color: #b0b0c0; + flex-shrink: 0; +} + +.pm-footer-right { + display: flex; + align-items: center; + gap: 6px; +} + +.pm-updated { + font-variant-numeric: tabular-nums; + color: #ccc; +} + +.pm-progress-tag { + display: inline-block; + width: 32px; + height: 6px; + background: #e8eaf6; + border-radius: 3px; + overflow: hidden; + flex-shrink: 0; +} + +.pm-progress-tag .pm-progress-fill { + display: block; + height: 100%; + width: 0%; + background: linear-gradient(90deg, #4f6ef7, #7c3aed); + border-radius: 3px; +} + +.pm-refresh { + background: none; + border: none; + color: #4f6ef7; + cursor: pointer; + font-weight: 500; + font-size: 13px; + padding: 0; + line-height: 1; + display: flex; + align-items: center; + justify-content: center; +} + +.pm-refresh:hover { + opacity: 0.7; + transform: rotate(180deg); + transition: transform 0.3s ease; +} + +.pm-hint-tip { + background: rgba(239, 68, 68, 0.08); + color: #ef4444; + font-weight: 600; +} + +.pm-toast { + position: fixed; + bottom: 20px; + left: 50%; + transform: translateX(-50%); + background: #333; + color: #fff; + font-size: 12px; + padding: 6px 16px; + border-radius: 8px; + z-index: 1000; + white-space: nowrap; + pointer-events: none; +} + +.pm-modal-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(0, 0, 0, 0.4); + display: flex; + align-items: center; + justify-content: center; + z-index: 2000; + animation: fadeIn 0.05s ease; +} + +@keyframes fadeIn { + from { opacity: 0.5; } + to { opacity: 1; } +} + +.pm-modal { + background: #fff; + border-radius: 12px; + padding: 20px; + min-width: 280px; + max-width: 360px; + box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15); + animation: slideIn 0.08s ease; +} + +@keyframes slideIn { + from { + opacity: 0.8; + transform: scale(0.98); + } + to { + opacity: 1; + transform: scale(1); + } +} + +.pm-modal-title { + font-size: 15px; + font-weight: 600; + color: #1a1a2e; + margin-bottom: 12px; +} + +.pm-modal-content { + font-size: 13px; + color: #666; + line-height: 1.5; + margin-bottom: 20px; +} + +.pm-modal-content strong { + color: #333; + font-weight: 600; +} + +.pm-modal-actions { + display: flex; + gap: 10px; + justify-content: flex-end; +} + +.pm-modal-btn { + padding: 8px 16px; + border-radius: 8px; + font-size: 13px; + font-weight: 500; + cursor: pointer; + transition: all 0.2s ease; + border: none; +} + +.pm-modal-btn-cancel { + background: #f0f0f0; + color: #666; +} + +.pm-modal-btn-cancel:hover { + background: #e0e0e0; +} + +.pm-modal-btn-confirm { + background: #ef4444; + color: #fff; +} + +.pm-modal-btn-confirm:hover { + background: #dc2626; +} + +@media (prefers-color-scheme: dark) { + .pm { + background: transparent; + color: #c9d1d9; + } + + .pm-header { + border-bottom-color: rgba(255, 255, 255, 0.1); + } + + .pm-hint-name { background: rgba(126, 231, 135, 0.15); color: #7ee787; } + .pm-hint-pid { background: rgba(210, 153, 34, 0.15); color: #d29922; } + .pm-hint-port { background: rgba(88, 166, 255, 0.15); color: #58a6ff; } + .pm-hint-path { background: rgba(255, 255, 255, 0.1); color: #8b949e; } + + .pm-empty { + color: #8b949e; + } + + .pm-item { + border-bottom-color: rgba(255, 255, 255, 0.1); + } + + .pm-item:hover { + background: rgba(255, 255, 255, 0.05); + } + + .pm-tag-name { background: rgba(126, 231, 135, 0.15); color: #7ee787; } + .pm-tag-pid { background: rgba(210, 153, 34, 0.15); color: #d29922; } + .pm-tag-port { background: rgba(88, 166, 255, 0.15); color: #58a6ff; } + .pm-tag-none { background: rgba(255, 255, 255, 0.1); color: #8b949e; } + + .pm-path { + color: #8b949e; + } + + .pm-path:hover { + background: rgba(255, 255, 255, 0.08); + } + + .pm-body::-webkit-scrollbar-track { + background: rgba(255, 255, 255, 0.05); + } + + .pm-body::-webkit-scrollbar-thumb { + background: rgba(255, 255, 255, 0.2); + } + + .pm-body::-webkit-scrollbar-thumb:hover { + background: rgba(255, 255, 255, 0.35); + } + + .pm-body { + scrollbar-color: rgba(255, 255, 255, 0.2) rgba(255, 255, 255, 0.05); + } + + .pm-hint-tip { + background: rgba(248, 81, 73, 0.15); + color: #fca5a5; + } + + .pm-toast { + background: #c9d1d9; + color: #0d1117; + } + + .pm-footer { + border-top-color: rgba(255, 255, 255, 0.1); + color: #8b949e; + } + + .pm-updated { + color: #484f58; + } + .pm-progress-tag { + background: rgba(255, 255, 255, 0.1); + } + .pm-progress-tag .pm-progress-fill { + background: linear-gradient(90deg, #58a6ff, #7ee787); + } + + .pm-refresh { + color: #58a6ff; + } + + .pm-refresh:hover { + opacity: 0.7; + transform: rotate(180deg); + transition: transform 0.3s ease; + } + + .pm-modal { + background: #161b22; + } + + .pm-modal-title { + color: #c9d1d9; + } + + .pm-modal-content { + color: #8b949e; + } + + .pm-modal-content strong { + color: #c9d1d9; + } + + .pm-modal-btn-cancel { + background: rgba(255, 255, 255, 0.1); + color: #8b949e; + } + + .pm-modal-btn-cancel:hover { + background: rgba(255, 255, 255, 0.15); + } + + .pm-modal-btn-confirm { + background: #da3633; + } + + .pm-modal-btn-confirm:hover { + background: #f85149; + } +} diff --git a/plugins/process-manager/src/ProcessManager/index.tsx b/plugins/process-manager/src/ProcessManager/index.tsx new file mode 100644 index 00000000..54808f4b --- /dev/null +++ b/plugins/process-manager/src/ProcessManager/index.tsx @@ -0,0 +1,166 @@ +import { useState, useRef, useEffect, useCallback } from 'react' +import { FiRefreshCw } from 'react-icons/fi' +import './index.css' +import { searchProcesses } from './search' +import { useProcessData } from './useProcessData' + +const CACHE_TTL = 5000 + +interface Props { + keyword: string +} + +export default function ProcessManager({ keyword }: Props) { + const { processes, loading, refresh, lastUpdated } = useProcessData() + const [toast, setToast] = useState('') + const [killModal, setKillModal] = useState<{ pid: number; name: string } | null>(null) + const [killing, setKilling] = useState(false) + const barRef = useRef(null) + const startRef = useRef(0) + const rafRef = useRef(0) + const toastTimer = useRef>(undefined) + + useEffect(() => { + const fill = barRef.current + if (!fill) return + const tick = () => { + const pct = Math.min((Date.now() - startRef.current) / CACHE_TTL * 100, 100) + fill.style.width = pct + '%' + rafRef.current = requestAnimationFrame(tick) + } + startRef.current = Date.now() + rafRef.current = requestAnimationFrame(tick) + return () => cancelAnimationFrame(rafRef.current) + }, []) + + useEffect(() => { + startRef.current = Date.now() + }, [lastUpdated]) + + const showToast = useCallback((msg: string) => { + setToast(msg) + clearTimeout(toastTimer.current) + toastTimer.current = setTimeout(() => setToast(''), 1500) + }, []) + + const copy = useCallback((text: string) => { + navigator.clipboard.writeText(text).then(() => showToast('已复制: ' + text)) + }, [showToast]) + + const confirmKill = useCallback((pid: number, name: string) => { + setKillModal({ pid, name }) + }, []) + + const executeKill = useCallback(async () => { + if (!killModal || killing) return + + setKilling(true) + const result = await window.services.killProcess(killModal.pid) + setKilling(false) + setKillModal(null) + + if (result.success) { + showToast(`已 Kill ${killModal.name} (PID: ${killModal.pid})`) + refresh(true) + } else { + showToast(`Kill 失败: ${result.error}`) + } + }, [killModal, killing, showToast, refresh]) + + const cancelKill = useCallback(() => { + if (killing) return + setKillModal(null) + }, [killing]) + + const handleContextMenu = useCallback((e: React.MouseEvent, pid: number, name: string) => { + e.preventDefault() + confirmKill(pid, name) + }, [confirmKill]) + + const filtered = searchProcesses(keyword, processes) + + return ( +
+
+
+ 进程名 + PID + 端口号 + 文件路径 +
+ 右键可 Kill 进程 +
+ + {toast &&
{toast}
} + +
+ {loading && processes.length === 0 && ( +
正在获取进程列表...
+ )} + + {!loading && filtered.length === 0 && ( +
+ {keyword ? '没有匹配的进程' : '暂无进程数据,请刷新'} +
+ )} + + {filtered.map(p => ( +
handleContextMenu(e, p.pid, p.name)} + > +
+
+
+ copy(p.name)}>{p.name} + copy(String(p.pid))}>{p.pid} + copy(p.path)} title={p.path}>{p.path} +
+
+ {p.ports.length > 0 + ? p.ports.map(port => ( + copy(String(port))}>{port} + )) + : 无端口占用 + } +
+
+
+
+ ))} +
+ +
+ 共 {filtered.length} 个结果 + + {lastUpdated || '--:--:--'} + + + +
+ + {killModal && ( +
+
e.stopPropagation()}> +
确认 Kill 进程
+
+ {killing + ? <>正在 Kill {killModal.name} (PID: {killModal.pid})... + : <>确定要 Kill 进程 {killModal.name} (PID: {killModal.pid}) 吗? + } +
+
+ + +
+
+
+ )} +
+ ) +} diff --git a/plugins/process-manager/src/ProcessManager/search.ts b/plugins/process-manager/src/ProcessManager/search.ts new file mode 100644 index 00000000..082f7f9f --- /dev/null +++ b/plugins/process-manager/src/ProcessManager/search.ts @@ -0,0 +1,54 @@ +function compareByPortPath(a: ProcessInfo, b: ProcessInfo): number { + const aHasPorts = a.ports.length > 0 + const bHasPorts = b.ports.length > 0 + const aHasPath = a.path.length > 0 + const bHasPath = b.path.length > 0 + + if (aHasPorts && aHasPath && !(bHasPorts && bHasPath)) return -1 + if (bHasPorts && bHasPath && !(aHasPorts && aHasPath)) return 1 + if (aHasPorts && !bHasPorts) return -1 + if (bHasPorts && !aHasPorts) return 1 + if (aHasPath && !bHasPath) return -1 + if (bHasPath && !aHasPath) return 1 + + return 0 +} + +export function searchProcesses(keyword: string, processes: ProcessInfo[]): ProcessInfo[] { + const kw = keyword.trim() + + if (!kw) { + return [...processes].sort(compareByPortPath) + } + + const lower = kw.toLowerCase() + + return processes + .map(p => { + let score = -1 + const nameLower = p.name.toLowerCase() + const pathLower = p.path.toLowerCase() + const pidStr = String(p.pid) + + // PID 精确匹配 + if (pidStr === kw) return { process: p, score: 110 } + + // PID/端口包含匹配 + if (pidStr.includes(kw)) score = Math.max(score, 90) + if (p.ports.some(port => String(port).includes(kw))) score = Math.max(score, 85) + + // 名称/路径匹配 + if (nameLower === lower) score = Math.max(score, 100) + else if (nameLower.startsWith(lower)) score = Math.max(score, 80) + else if (nameLower.includes(lower)) score = Math.max(score, 60) + else if (pathLower.includes(lower)) score = Math.max(score, 50) + + return { process: p, score } + }) + .filter(s => s.score >= 0) + .sort((a, b) => { + if (b.score !== a.score) return b.score - a.score + return compareByPortPath(a.process, b.process) + }) + .map(s => s.process) +} diff --git a/plugins/process-manager/src/ProcessManager/useProcessData.ts b/plugins/process-manager/src/ProcessManager/useProcessData.ts new file mode 100644 index 00000000..51012d22 --- /dev/null +++ b/plugins/process-manager/src/ProcessManager/useProcessData.ts @@ -0,0 +1,60 @@ +import { useState, useEffect, useCallback, useRef } from 'react' + +const CACHE_TTL = 5000 + +function mergeData(rawList: ProcessRaw[], rawPorts: PortEntry[]): ProcessInfo[] { + const portMap: Record = {} + for (const entry of rawPorts) { + if (!portMap[entry.pid]) portMap[entry.pid] = [] + if (!portMap[entry.pid].includes(entry.port)) { + portMap[entry.pid].push(entry.port) + } + } + + return rawList.map(p => ({ + pid: p.pid, + name: p.name, + path: p.path, + ports: portMap[p.pid] || [] + })) +} + +export function useProcessData() { + const [processes, setProcesses] = useState([]) + const [loading, setLoading] = useState(false) + const [lastUpdated, setLastUpdated] = useState('') + const lastFetchRef = useRef(0) + + const refresh = useCallback(async (force?: boolean) => { + const now = Date.now() + if (!force && now - lastFetchRef.current < CACHE_TTL) return + + if (!window.services?.listProcesses) { + console.warn('services not available') + return + } + + setLoading(true) + try { + const [list, ports] = await Promise.all([ + window.services.listProcesses(), + window.services.scanPorts() + ]) + setProcesses(mergeData(list || [], ports || [])) + setLastUpdated(new Date().toLocaleTimeString()) + lastFetchRef.current = now + } catch (e) { + console.error('useProcessData error:', e) + } finally { + setLoading(false) + } + }, []) + + useEffect(() => { + refresh() + const timer = setInterval(refresh, CACHE_TTL) + return () => clearInterval(timer) + }, [refresh]) + + return { processes, loading, refresh, lastUpdated } +} diff --git a/plugins/process-manager/src/env.d.ts b/plugins/process-manager/src/env.d.ts new file mode 100644 index 00000000..8f864562 --- /dev/null +++ b/plugins/process-manager/src/env.d.ts @@ -0,0 +1,36 @@ +/// +/// + +interface ProcessRaw { + name: string + pid: number + path: string +} + +interface PortEntry { + port: number + pid: number + protocol: string +} + +interface KillResult { + success: boolean + error?: string +} + +interface ProcessInfo { + pid: number + name: string + path: string + ports: number[] +} + +interface Services { + listProcesses: () => Promise + scanPorts: () => Promise + killProcess: (pid: number) => Promise +} + +interface Window { + services: Services +} diff --git a/plugins/process-manager/src/main.css b/plugins/process-manager/src/main.css new file mode 100644 index 00000000..ac61ff74 --- /dev/null +++ b/plugins/process-manager/src/main.css @@ -0,0 +1,14 @@ +html, body { + margin: 0; + padding: 0; +} + +@media (prefers-color-scheme: light) { + +} + +@media (prefers-color-scheme: dark) { + body { + color: #fff; + } +} diff --git a/plugins/process-manager/src/main.tsx b/plugins/process-manager/src/main.tsx new file mode 100644 index 00000000..73fb2013 --- /dev/null +++ b/plugins/process-manager/src/main.tsx @@ -0,0 +1,6 @@ +import React from 'react' +import ReactDOM from 'react-dom/client' +import './main.css' +import App from './App' + +ReactDOM.createRoot(document.getElementById('root')!).render() diff --git a/plugins/process-manager/tsconfig.json b/plugins/process-manager/tsconfig.json new file mode 100644 index 00000000..d49c64de --- /dev/null +++ b/plugins/process-manager/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": false, + "noImplicitAny": false, + "types": ["@ztools-center/ztools-api-types"] + }, + "include": ["src"] +} diff --git a/plugins/process-manager/vite.config.js b/plugins/process-manager/vite.config.js new file mode 100644 index 00000000..2d511098 --- /dev/null +++ b/plugins/process-manager/vite.config.js @@ -0,0 +1,8 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' + +// https://vitejs.dev/config/ +export default defineConfig({ + plugins: [react()], + base: './' +})