From 50706645932fb1db75c893de3b5b649bca268126 Mon Sep 17 00:00:00 2001 From: cnbugs Date: Sat, 18 Jul 2026 19:19:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20ConfTempl?= =?UTF-8?q?ate=20=E4=BA=91=E5=8E=9F=E7=94=9F=E9=85=8D=E7=BD=AE=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=94=9F=E6=88=90=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Vue 3 + Element Plus + Monaco Editor 技术栈 - 支持 NGINX、Redis、MySQL、K8s Deployment、K8s Service 五个组件 - 侧边栏搜索过滤、动态表单联动、实时代码预览 - 一键复制/下载/重置,localStorage 状态持久化 - 纯前端静态页面,零后端依赖 --- .gitignore | 5 + README.md | 212 ++++ index.html | 13 + package-lock.json | 2122 ++++++++++++++++++++++++++++++++ package.json | 22 + src/App.vue | 109 ++ src/components/CodePreview.vue | 192 +++ src/components/ConfigForm.vue | 258 ++++ src/components/Sidebar.vue | 239 ++++ src/main.js | 14 + src/schemas/index.js | 65 + src/schemas/k8s-deployment.js | 416 +++++++ src/schemas/k8s-service.js | 220 ++++ src/schemas/mysql.js | 518 ++++++++ src/schemas/nginx.js | 339 +++++ src/schemas/redis.js | 319 +++++ vite.config.js | 21 + 17 files changed, 5084 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 src/App.vue create mode 100644 src/components/CodePreview.vue create mode 100644 src/components/ConfigForm.vue create mode 100644 src/components/Sidebar.vue create mode 100644 src/main.js create mode 100644 src/schemas/index.js create mode 100644 src/schemas/k8s-deployment.js create mode 100644 src/schemas/k8s-service.js create mode 100644 src/schemas/mysql.js create mode 100644 src/schemas/nginx.js create mode 100644 src/schemas/redis.js create mode 100644 vite.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7799c77 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +dist/ +.DS_Store +*.local +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..9eca24b --- /dev/null +++ b/README.md @@ -0,0 +1,212 @@ +# ⚙️ ConfTemplate — 云原生配置文件生成器 + +> 一站式可视化生成 NGINX、Redis、MySQL、Kubernetes 等主流中间件的标准配置文件,告别手写配置的语法烦恼。 + +![Vue 3](https://img.shields.io/badge/Vue-3-42b883?logo=vue.js) +![Element Plus](https://img.shields.io/badge/Element_Plus-2.x-409eff) +![Monaco Editor](https://img.shields.io/badge/Monaco_Editor-0.49-1e90ff) +![License](https://img.shields.io/badge/License-MIT-green) + +--- + +## 📸 界面预览 + +三栏布局:**左侧边栏导航** → **中间表单配置** → **右侧代码预览** + +- 侧边栏支持搜索过滤,快速定位目标中间件 +- 表单支持下拉框、输入框、开关、数字输入等多种组件 +- 代码预览基于 Monaco Editor(VS Code 内核),支持语法高亮 +- 一键复制、一键下载、一键重置 + +--- + +## ✨ 核心特性 + +| 特性 | 说明 | +|------|------| +| 🔍 搜索过滤 | 侧边栏支持关键字搜索,输入 `k8s` 即可过滤出 Kubernetes 相关模板 | +| 📋 动态表单 | 基于 JSON Schema 驱动,自动渲染专属配置表单 | +| 🔗 联动逻辑 | 选项变更时动态展示/隐藏关联参数(如开启 HTTPS 后显示证书路径) | +| 💡 最佳实践预设 | 所有参数内置生产环境推荐默认值 | +| 📝 实时预览 | 表单修改即时反映到右侧代码,Monaco Editor 专业语法高亮 | +| 📋 一键复制 | 生成的配置一键复制到系统剪贴板 | +| 💾 一键下载 | 直接保存为 `.conf` / `.cnf` / `.yaml` 本地文件 | +| 🔄 一键重置 | 快速恢复当前组件的默认模板状态 | +| 💾 状态持久化 | 表单数据自动保存到 localStorage,刷新页面不丢失 | +| 🚫 零后端 | 纯前端静态页面,无需 API、无需数据库、无需登录 | + +--- + +## 🧩 一期支持组件 + +| 分类 | 组件 | 输出格式 | 核心配置项 | +|------|------|----------|------------| +| 代理与 Web | **NGINX** | `.conf` | HTTP/HTTPS 端口、域名、SSL 证书、反向代理、Gzip、CORS | +| 缓存 | **Redis** | `.conf` | 单机/哨兵/集群模式、端口、内存限制、RDB/AOF 持久化、密码 | +| 数据库 | **MySQL** | `.cnf` | 版本(5.7/8.0)、Buffer Pool 自动计算、字符集、慢查询、主从复制 | +| 云原生 | **K8s Deployment** | `.yaml` | 镜像、副本数、资源限制、健康检查、滚动更新策略 | +| 云原生 | **K8s Service** | `.yaml` | ClusterIP/NodePort/LoadBalancer、端口映射、会话保持 | + +--- + +## 🚀 快速开始 + +### 环境要求 + +- Node.js >= 18 +- npm >= 9 + +### 安装与运行 + +```bash +# 克隆项目 +git clone +cd conf-template + +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev +``` + +浏览器访问 `http://localhost:3000` + +### 生产构建 + +```bash +# 构建产物输出到 dist/ 目录 +npm run build + +# 预览构建产物 +npm run preview +``` + +构建产物为纯静态文件,可直接部署到 Nginx、GitHub Pages、Vercel、Cloudflare Pages 等任意静态托管服务。 + +--- + +## 📁 项目结构 + +``` +conf-template/ +├── index.html # 入口 HTML +├── package.json # 项目依赖与脚本 +├── vite.config.js # Vite 构建配置 +├── src/ +│ ├── main.js # Vue 应用入口 +│ ├── App.vue # 主布局(三栏) +│ ├── components/ +│ │ ├── Sidebar.vue # 侧边栏导航 + 搜索过滤 +│ │ ├── ConfigForm.vue # 左侧动态表单面板 +│ │ └── CodePreview.vue # 右侧 Monaco Editor 代码预览 +│ └── schemas/ +│ ├── index.js # Schema 注册中心 + 工具函数 +│ ├── nginx.js # NGINX Schema 定义 + 模板生成 +│ ├── redis.js # Redis Schema 定义 + 模板生成 +│ ├── mysql.js # MySQL Schema 定义 + 模板生成 +│ ├── k8s-deployment.js # K8s Deployment Schema + YAML 生成 +│ └── k8s-service.js # K8s Service Schema + YAML 生成 +└── dist/ # 生产构建产物(gitignore) +``` + +--- + +## 🔧 如何扩展新组件 + +每个中间件由两部分组成:**Schema 定义** + **模板生成函数**。 + +### 1. 创建 Schema 文件 + +在 `src/schemas/` 下新建文件,如 `postgresql.js`: + +```js +export const postgresqlSchema = { + id: 'postgresql', + name: 'PostgreSQL', + icon: 'Coin', + category: '数据库', + description: '世界上最先进的开源关系型数据库', + format: 'conf', + fileName: 'postgresql.conf', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'port', + label: '监听端口', + type: 'number', // number | text | select | switch + min: 1, + max: 65535, + default: 5432, + }, + // ...更多字段 + ], + }, + ], +} + +export function generatePostgresqlConf(config) { + // 根据 config 对象生成配置文件文本 + return `port = ${config.port}\n...` +} +``` + +### 2. 字段类型说明 + +| type | 渲染组件 | 额外属性 | +|------|----------|----------| +| `select` | 下拉框 | `options: [{label, value}]` | +| `number` | 数字输入 | `min`, `max`, `step` | +| `text` | 文本输入 | `placeholder` | +| `switch` | 开关 | — | + +### 3. 联动字段 + +通过 `dependsOn` 实现条件显示: + +```js +{ + key: 'sslCert', + label: 'SSL 证书路径', + type: 'text', + dependsOn: { key: 'enableSsl', value: true }, // 仅当 enableSsl === true 时显示 +} +``` + +### 4. 注册到 Schema 中心 + +在 `src/schemas/index.js` 中导入并注册: + +```js +import { postgresqlSchema, generatePostgresqlConf } from './postgresql' + +export const schemas = { + // ...已有组件 + postgresql: postgresqlSchema, +} + +export const generators = { + // ...已有生成器 + postgresql: generatePostgresqlConf, +} +``` + +--- + +## 🛠 技术栈 + +| 技术 | 用途 | +|------|------| +| [Vue 3](https://vuejs.org/) | 核心视图框架,原生双向绑定 | +| [Element Plus](https://element-plus.org/) | UI 组件库(表单、按钮、提示等) | +| [Monaco Editor](https://microsoft.github.io/monaco-editor/) | 代码编辑器(VS Code 内核) | +| [Vite](https://vitejs.dev/) | 构建工具,秒级 HMR | +| localStorage | 用户配置本地持久化 | + +--- + +## 📄 License + +[MIT](LICENSE) diff --git a/index.html b/index.html new file mode 100644 index 0000000..7a55506 --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + ConfTemplate - 云原生配置文件生成器 + + + +
+ + + diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..5ad1e1b --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2122 @@ +{ + "name": "conf-template", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "conf-template", + "version": "1.0.0", + "dependencies": { + "@element-plus/icons-vue": "^2.3.0", + "@vitejs/plugin-vue": "^5.2.4", + "element-plus": "^2.7.0", + "monaco-editor": "^0.49.0", + "unplugin-auto-import": "^0.18.6", + "unplugin-vue-components": "^0.27.5", + "vite": "^5.4.21", + "vue": "^3.4.0", + "vue-router": "^4.3.0" + } + }, + "node_modules/@antfu/utils": { + "version": "0.7.10", + "resolved": "https://registry.npmjs.org/@antfu/utils/-/utils-0.7.10.tgz", + "integrity": "sha512-+562v9k4aI80m1+VuMHehNJWLOFjBnXn3tdOitzD0il5b7smkSBal4+a3oKiQTbrwMmN/TBUMDvbdoWDehgOww==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.7" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@ctrl/tinycolor": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@ctrl/tinycolor/-/tinycolor-4.2.0.tgz", + "integrity": "sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/@element-plus/icons-vue": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/@element-plus/icons-vue/-/icons-vue-2.3.2.tgz", + "integrity": "sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==", + "license": "MIT", + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@floating-ui/core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.8.0.tgz", + "integrity": "sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==", + "license": "MIT", + "dependencies": { + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.8.0.tgz", + "integrity": "sha512-yXSrzeHZBTZadLOlfyhCkJHNeLJnHRnRInwdZ40L7ZiaAtrBwoYlsDrX3v5zB1Utk7CLfzcOVnVVWoXEky7Ceg==", + "license": "MIT", + "dependencies": { + "@floating-ui/core": "^1.8.0", + "@floating-ui/utils": "^0.2.12" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.12.tgz", + "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "license": "MIT" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@popperjs/core": { + "name": "@sxzz/popperjs-es", + "version": "2.11.8", + "resolved": "https://registry.npmjs.org/@sxzz/popperjs-es/-/popperjs-es-2.11.8.tgz", + "integrity": "sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/popperjs" + } + }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", + "integrity": "sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.2.tgz", + "integrity": "sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.2.tgz", + "integrity": "sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.2.tgz", + "integrity": "sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.2.tgz", + "integrity": "sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.2.tgz", + "integrity": "sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.2.tgz", + "integrity": "sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==", + "cpu": [ + "arm" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.2.tgz", + "integrity": "sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==", + "cpu": [ + "arm" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.2.tgz", + "integrity": "sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==", + "cpu": [ + "arm64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.2.tgz", + "integrity": "sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==", + "cpu": [ + "arm64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.2.tgz", + "integrity": "sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==", + "cpu": [ + "loong64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.2.tgz", + "integrity": "sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==", + "cpu": [ + "loong64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.2.tgz", + "integrity": "sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==", + "cpu": [ + "ppc64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.2.tgz", + "integrity": "sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==", + "cpu": [ + "ppc64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.2.tgz", + "integrity": "sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==", + "cpu": [ + "riscv64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.2.tgz", + "integrity": "sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==", + "cpu": [ + "riscv64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.2.tgz", + "integrity": "sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==", + "cpu": [ + "s390x" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.2.tgz", + "integrity": "sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==", + "cpu": [ + "x64" + ], + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.2.tgz", + "integrity": "sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==", + "cpu": [ + "x64" + ], + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.2.tgz", + "integrity": "sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.2.tgz", + "integrity": "sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.2.tgz", + "integrity": "sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.2.tgz", + "integrity": "sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.2.tgz", + "integrity": "sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.2.tgz", + "integrity": "sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "license": "MIT" + }, + "node_modules/@types/lodash": { + "version": "4.17.24", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", + "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", + "license": "MIT" + }, + "node_modules/@types/lodash-es": { + "version": "4.17.12", + "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz", + "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==", + "license": "MIT", + "dependencies": { + "@types/lodash": "*" + } + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.40.tgz", + "integrity": "sha512-39E8IgOhTbVDnoJFMKc2DvYnypcZwUqgUhQkccva/0m6FUwtIKSGV7n1hpVmYcFaoRAwf9pBcwnKlCEsN63ZEQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/shared": "3.5.40", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.40.tgz", + "integrity": "sha512-pwkx4vqlqOspFstrcmzwkKLePVMD3PT65imRzLhanU2V1Fj4K13g6OXjanOyzw3aTAuRk84BOmY8f3rEHqPaVA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.40.tgz", + "integrity": "sha512-gIf497P4kpuALcvs5n3AEg1Vdn0pSY4XbjASIfHNYF1/MP3T2Mf2STERTubysBxCRxzJGJYtF/O7vwJrxFB3Vw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.7", + "@vue/compiler-core": "3.5.40", + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-ssr": "3.5.40", + "@vue/shared": "3.5.40", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.19", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.40.tgz", + "integrity": "sha512-rrE5xiXG663+vHCHa3J9p2z5OcBRjXmoqenprJxAFQxg5pSshzeBiCE6pu46axapRJ2Adk0YDA2BRZVjiHXnhg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.6.4", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz", + "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==", + "license": "MIT" + }, + "node_modules/@vue/reactivity": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.40.tgz", + "integrity": "sha512-B7ot9UlUZOi1zbq61/LvE88ZLTV8IlajTdiZTAEiDQgrnIMIZoPr9kGw0Zw46ObW62O9+H/Be3kMbfb7kYPQZA==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.40.tgz", + "integrity": "sha512-KAZLweuZ6uUJPK1PMSQPgBU5gCjgrrfjUhSglmU9NhH+Zjepa8cnwSydPWDWHDwOgY4g3VcZ+PljbiHlURNCbw==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.40.tgz", + "integrity": "sha512-ZfrX8ssZQds900L9pr8AuK05ddnMsR4MPMZr8cPN9GoqoPWcXLhjvvbIA2SMv+7a97sJ1vv9pj/zxK0Cq/eEFQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.40", + "@vue/runtime-core": "3.5.40", + "@vue/shared": "3.5.40", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.40.tgz", + "integrity": "sha512-XNJym9WpevhTVt1HuwOrCRJ5Q+9z4BjTMrDtjTrvx74SmUll8spNTw6whWJa9mEkO4PKn5TihI/bm/8ds2QVJw==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/shared": "3.5.40" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.40.tgz", + "integrity": "sha512-WxnBtruIqOoV3rA4jeKDWzrYI5h7Cp4+pjwDi8kWGHz+IslhiN+wguLVVhtv2l8VoU02rzDCVfDjgCl1lNpZVg==", + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-14.3.0.tgz", + "integrity": "sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==", + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "14.3.0", + "@vueuse/shared": "14.3.0" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/@vueuse/metadata": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-14.3.0.tgz", + "integrity": "sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "14.3.0", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-14.3.0.tgz", + "integrity": "sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/anymatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/async-validator": { + "version": "4.2.5", + "resolved": "https://registry.npmjs.org/async-validator/-/async-validator-4.2.5.tgz", + "integrity": "sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==", + "license": "MIT" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz", + "integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==", + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.21", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.21.tgz", + "integrity": "sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/element-plus": { + "version": "2.14.3", + "resolved": "https://registry.npmjs.org/element-plus/-/element-plus-2.14.3.tgz", + "integrity": "sha512-pJcvxcpZjYruNzuJhAeVwnbYjfNgzBKnWHwSVEhwzM2/kcLI3brzmtIBxtPqd4hQWJfD1PRnjoc1WipLw2eBGg==", + "license": "MIT", + "dependencies": { + "@ctrl/tinycolor": "^4.2.0", + "@element-plus/icons-vue": "^2.3.2", + "@floating-ui/dom": "^1.7.6", + "@popperjs/core": "npm:@sxzz/popperjs-es@^2.11.8", + "@types/lodash": "^4.17.24", + "@types/lodash-es": "^4.17.12", + "@vueuse/core": "14.3.0", + "async-validator": "^4.2.5", + "dayjs": "^1.11.20", + "lodash": "^4.18.1", + "lodash-es": "^4.18.1", + "lodash-unified": "^1.0.3", + "memoize-one": "^6.0.0", + "normalize-wheel-es": "^1.2.0", + "vue-component-type-helpers": "^3.3.5" + }, + "peerDependencies": { + "vue": "^3.3.7" + } + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "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" + } + }, + "node_modules/escape-string-regexp": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz", + "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/exsolve": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.1.0.tgz", + "integrity": "sha512-D+42+T12DdIlJM3uepa55qGiL3sYdLBOxIl2ifQCzCHz4c7eiolaHsi3BIqEr7JxBzxv2pYZQX9kw16ziMcEmw==", + "license": "MIT" + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/lodash": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "license": "MIT" + }, + "node_modules/lodash-es": { + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.18.1.tgz", + "integrity": "sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==", + "license": "MIT" + }, + "node_modules/lodash-unified": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/lodash-unified/-/lodash-unified-1.0.3.tgz", + "integrity": "sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==", + "license": "MIT", + "peerDependencies": { + "@types/lodash-es": "*", + "lodash": "*", + "lodash-es": "*" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/memoize-one": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", + "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", + "license": "MIT" + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/minimatch": { + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.2" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/monaco-editor": { + "version": "0.49.0", + "resolved": "https://registry.npmjs.org/monaco-editor/-/monaco-editor-0.49.0.tgz", + "integrity": "sha512-2I8/T3X/hLxB2oPHgqcNYUVdA/ZEFShT7IAujifIPMfKkNbLOqY8XCoyHCXrsdjb36dW9MwoTwBCFpXKMwNwaQ==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-wheel-es": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/normalize-wheel-es/-/normalize-wheel-es-1.2.0.tgz", + "integrity": "sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==", + "license": "BSD-3-Clause" + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/postcss": { + "version": "8.5.19", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.19.tgz", + "integrity": "sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.12", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/quansync": { + "version": "0.2.11", + "resolved": "https://registry.npmjs.org/quansync/-/quansync-0.2.11.tgz", + "integrity": "sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/antfu" + }, + { + "type": "individual", + "url": "https://github.com/sponsors/sxzz" + } + ], + "license": "MIT" + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/readdirp/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.62.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.2.tgz", + "integrity": "sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "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.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scule": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/scule/-/scule-1.3.0.tgz", + "integrity": "sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==", + "license": "MIT" + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "license": "MIT" + }, + "node_modules/unimport": { + "version": "3.14.6", + "resolved": "https://registry.npmjs.org/unimport/-/unimport-3.14.6.tgz", + "integrity": "sha512-CYvbDaTT04Rh8bmD8jz3WPmHYZRG/NnvYVzwD6V1YAlvvKROlAeNDUBhkBGzNav2RKaeuXvlWYaa1V4Lfi/O0g==", + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.4", + "acorn": "^8.14.0", + "escape-string-regexp": "^5.0.0", + "estree-walker": "^3.0.3", + "fast-glob": "^3.3.3", + "local-pkg": "^1.0.0", + "magic-string": "^0.30.17", + "mlly": "^1.7.4", + "pathe": "^2.0.1", + "picomatch": "^4.0.2", + "pkg-types": "^1.3.0", + "scule": "^1.3.0", + "strip-literal": "^2.1.1", + "unplugin": "^1.16.1" + } + }, + "node_modules/unimport/node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "license": "MIT" + }, + "node_modules/unimport/node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/unimport/node_modules/local-pkg": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-1.2.1.tgz", + "integrity": "sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==", + "license": "MIT", + "dependencies": { + "mlly": "^1.7.4", + "pkg-types": "^2.3.0", + "quansync": "^0.2.11" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/unimport/node_modules/local-pkg/node_modules/pkg-types": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.1.tgz", + "integrity": "sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==", + "license": "MIT", + "dependencies": { + "confbox": "^0.2.4", + "exsolve": "^1.0.8", + "pathe": "^2.0.3" + } + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/unplugin-auto-import": { + "version": "0.18.6", + "resolved": "https://registry.npmjs.org/unplugin-auto-import/-/unplugin-auto-import-0.18.6.tgz", + "integrity": "sha512-LMFzX5DtkTj/3wZuyG5bgKBoJ7WSgzqSGJ8ppDRdlvPh45mx6t6w3OcbExQi53n3xF5MYkNGPNR/HYOL95KL2A==", + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "unimport": "^3.13.4", + "unplugin": "^1.16.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@nuxt/kit": "^3.2.2", + "@vueuse/core": "*" + }, + "peerDependenciesMeta": { + "@nuxt/kit": { + "optional": true + }, + "@vueuse/core": { + "optional": true + } + } + }, + "node_modules/unplugin-vue-components": { + "version": "0.27.5", + "resolved": "https://registry.npmjs.org/unplugin-vue-components/-/unplugin-vue-components-0.27.5.tgz", + "integrity": "sha512-m9j4goBeNwXyNN8oZHHxvIIYiG8FQ9UfmKWeNllpDvhU7btKNNELGPt+o3mckQKuPwrE7e0PvCsx+IWuDSD9Vg==", + "license": "MIT", + "dependencies": { + "@antfu/utils": "^0.7.10", + "@rollup/pluginutils": "^5.1.3", + "chokidar": "^3.6.0", + "debug": "^4.3.7", + "fast-glob": "^3.3.2", + "local-pkg": "^0.5.1", + "magic-string": "^0.30.14", + "minimatch": "^9.0.5", + "mlly": "^1.7.3", + "unplugin": "^1.16.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@babel/parser": "^7.15.8", + "@nuxt/kit": "^3.2.2", + "vue": "2 || 3" + }, + "peerDependenciesMeta": { + "@babel/parser": { + "optional": true + }, + "@nuxt/kit": { + "optional": true + } + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "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 + } + } + }, + "node_modules/vue": { + "version": "3.5.40", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.40.tgz", + "integrity": "sha512-+8PJ4SJXdn/cHGImF4CKdxlWHIN5Dkt7DoufRREM6h6uVCx2m7QxgcEQmmzyOK8A9mcafg7sFbJFYsdFVubTig==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.40", + "@vue/compiler-sfc": "3.5.40", + "@vue/runtime-dom": "3.5.40", + "@vue/server-renderer": "3.5.40", + "@vue/shared": "3.5.40" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/vue-component-type-helpers": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.7.tgz", + "integrity": "sha512-Skkhw9agYSgsWqv7bxSOGJZa9SaiJbZVGdXuFWnrzKaQYHnw9qbjD630rw6RyMqDbp54nfLCLw5SZA55if7JLg==", + "license": "MIT" + }, + "node_modules/vue-router": { + "version": "4.6.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz", + "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==", + "license": "MIT", + "dependencies": { + "@vue/devtools-api": "^6.6.4" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.5.0" + } + }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "license": "MIT" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..105c0a4 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "name": "conf-template", + "version": "1.0.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.0", + "@vitejs/plugin-vue": "^5.2.4", + "element-plus": "^2.7.0", + "monaco-editor": "^0.49.0", + "unplugin-auto-import": "^0.18.6", + "unplugin-vue-components": "^0.27.5", + "vite": "^5.4.21", + "vue": "^3.4.0", + "vue-router": "^4.3.0" + } +} diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..64536ec --- /dev/null +++ b/src/App.vue @@ -0,0 +1,109 @@ + + + + + diff --git a/src/components/CodePreview.vue b/src/components/CodePreview.vue new file mode 100644 index 0000000..96c86c2 --- /dev/null +++ b/src/components/CodePreview.vue @@ -0,0 +1,192 @@ + + + + + diff --git a/src/components/ConfigForm.vue b/src/components/ConfigForm.vue new file mode 100644 index 0000000..b9177b9 --- /dev/null +++ b/src/components/ConfigForm.vue @@ -0,0 +1,258 @@ + + + + + diff --git a/src/components/Sidebar.vue b/src/components/Sidebar.vue new file mode 100644 index 0000000..fe60d11 --- /dev/null +++ b/src/components/Sidebar.vue @@ -0,0 +1,239 @@ + + + + + diff --git a/src/main.js b/src/main.js new file mode 100644 index 0000000..63b00c7 --- /dev/null +++ b/src/main.js @@ -0,0 +1,14 @@ +import { createApp } from 'vue' +import ElementPlus from 'element-plus' +import 'element-plus/dist/index.css' +import * as ElementPlusIconsVue from '@element-plus/icons-vue' +import App from './App.vue' + +const app = createApp(App) +app.use(ElementPlus, { size: 'default' }) + +for (const [key, component] of Object.entries(ElementPlusIconsVue)) { + app.component(key, component) +} + +app.mount('#app') diff --git a/src/schemas/index.js b/src/schemas/index.js new file mode 100644 index 0000000..113418c --- /dev/null +++ b/src/schemas/index.js @@ -0,0 +1,65 @@ +import { nginxSchema, generateNginxConf } from './nginx' +import { redisSchema, generateRedisConf } from './redis' +import { mysqlSchema, generateMysqlConf } from './mysql' +import { k8sDeploymentSchema, generateK8sDeploymentYaml } from './k8s-deployment' +import { k8sServiceSchema, generateK8sServiceYaml } from './k8s-service' + +export const schemas = { + nginx: nginxSchema, + redis: redisSchema, + mysql: mysqlSchema, + 'k8s-deployment': k8sDeploymentSchema, + 'k8s-service': k8sServiceSchema, +} + +export const generators = { + nginx: generateNginxConf, + redis: generateRedisConf, + mysql: generateMysqlConf, + 'k8s-deployment': generateK8sDeploymentYaml, + 'k8s-service': generateK8sServiceYaml, +} + +export const categories = [ + { + name: '代理与Web', + icon: 'Monitor', + items: ['nginx'], + }, + { + name: '缓存', + icon: 'Coin', + items: ['redis'], + }, + { + name: '数据库', + icon: 'Coin', + items: ['mysql'], + }, + { + name: '云原生', + icon: 'Box', + items: ['k8s-deployment', 'k8s-service'], + }, +] + +export function getDefaultValues(schema) { + const defaults = {} + for (const group of schema.groups) { + for (const field of group.fields) { + defaults[field.key] = field.default + } + } + return defaults +} + +export function getLanguage(format) { + const map = { + conf: 'ini', + cnf: 'ini', + yaml: 'yaml', + json: 'json', + toml: 'toml', + } + return map[format] || 'plaintext' +} diff --git a/src/schemas/k8s-deployment.js b/src/schemas/k8s-deployment.js new file mode 100644 index 0000000..aadbc01 --- /dev/null +++ b/src/schemas/k8s-deployment.js @@ -0,0 +1,416 @@ +export const k8sDeploymentSchema = { + id: 'k8s-deployment', + name: 'K8s Deployment', + icon: 'Box', + category: '云原生', + description: 'Kubernetes 无状态应用部署控制器', + format: 'yaml', + fileName: 'deployment.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'appName', + label: '应用名称', + type: 'text', + placeholder: 'my-app', + default: 'my-app', + required: true, + tip: '将用作 Deployment 和 Label 的名称', + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'image', + label: '容器镜像地址', + type: 'text', + placeholder: 'nginx:1.24', + default: 'nginx:1.24', + required: true, + }, + { + key: 'replicas', + label: '副本数', + type: 'number', + min: 1, + max: 100, + default: 3, + tip: '建议生产环境至少 2 副本', + }, + ], + }, + { + title: '容器端口', + fields: [ + { + key: 'containerPort', + label: '容器端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + }, + { + key: 'protocol', + label: '协议', + type: 'select', + options: [ + { label: 'TCP', value: 'TCP' }, + { label: 'UDP', value: 'UDP' }, + ], + default: 'TCP', + }, + { + key: 'portName', + label: '端口名称', + type: 'text', + placeholder: 'http', + default: 'http', + tip: 'Service 匹配时使用', + }, + ], + }, + { + title: '资源限制', + fields: [ + { + key: 'cpuRequest', + label: 'CPU Requests', + type: 'select', + options: [ + { label: '50m', value: '50m' }, + { label: '100m', value: '100m' }, + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + ], + default: '100m', + }, + { + key: 'cpuLimit', + label: 'CPU Limits', + type: 'select', + options: [ + { label: '200m', value: '200m' }, + { label: '500m', value: '500m' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '4', value: '4' }, + ], + default: '500m', + }, + { + key: 'memoryRequest', + label: 'Memory Requests', + type: 'select', + options: [ + { label: '64Mi', value: '64Mi' }, + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + ], + default: '128Mi', + }, + { + key: 'memoryLimit', + label: 'Memory Limits', + type: 'select', + options: [ + { label: '128Mi', value: '128Mi' }, + { label: '256Mi', value: '256Mi' }, + { label: '512Mi', value: '512Mi' }, + { label: '1Gi', value: '1Gi' }, + { label: '2Gi', value: '2Gi' }, + ], + default: '256Mi', + }, + ], + }, + { + title: '健康检查', + fields: [ + { + key: 'enableLivenessProbe', + label: '存活探针 (Liveness)', + type: 'switch', + default: true, + tip: '检测容器是否存活,失败则重启', + }, + { + key: 'livenessProbeType', + label: '探针方式', + type: 'select', + options: [ + { label: 'HTTP GET', value: 'httpGet' }, + { label: 'TCP Socket', value: 'tcpSocket' }, + { label: 'Exec Command', value: 'exec' }, + ], + default: 'httpGet', + dependsOn: { key: 'enableLivenessProbe', value: true }, + }, + { + key: 'livenessPath', + label: '健康检查路径', + type: 'text', + placeholder: '/healthz', + default: '/healthz', + dependsOn: { key: 'livenessProbeType', value: 'httpGet' }, + }, + { + key: 'livenessPort', + label: '探针端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + dependsOn: { key: 'enableLivenessProbe', value: true }, + }, + { + key: 'enableReadinessProbe', + label: '就绪探针 (Readiness)', + type: 'switch', + default: true, + tip: '检测容器是否就绪接收流量', + }, + { + key: 'readinessProbeType', + label: '探针方式', + type: 'select', + options: [ + { label: 'HTTP GET', value: 'httpGet' }, + { label: 'TCP Socket', value: 'tcpSocket' }, + { label: 'Exec Command', value: 'exec' }, + ], + default: 'httpGet', + dependsOn: { key: 'enableReadinessProbe', value: true }, + }, + { + key: 'readinessPath', + label: '就绪检查路径', + type: 'text', + placeholder: '/ready', + default: '/ready', + dependsOn: { key: 'readinessProbeType', value: 'httpGet' }, + }, + { + key: 'readinessPort', + label: '探针端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + dependsOn: { key: 'enableReadinessProbe', value: true }, + }, + ], + }, + { + title: '部署策略', + fields: [ + { + key: 'strategy', + label: '更新策略', + type: 'select', + options: [ + { label: 'RollingUpdate - 滚动更新 (推荐)', value: 'RollingUpdate' }, + { label: 'Recreate - 先删后建', value: 'Recreate' }, + ], + default: 'RollingUpdate', + }, + { + key: 'maxSurge', + label: '最大超出副本数', + type: 'select', + options: [ + { label: '1', value: '1' }, + { label: '25%', value: '25%' }, + { label: '50%', value: '50%' }, + { label: '100%', value: '100%' }, + ], + default: '25%', + dependsOn: { key: 'strategy', value: 'RollingUpdate' }, + }, + { + key: 'maxUnavailable', + label: '最大不可用副本数', + type: 'select', + options: [ + { label: '0 (零停机)', value: '0' }, + { label: '1', value: '1' }, + { label: '25%', value: '25%' }, + ], + default: '0', + dependsOn: { key: 'strategy', value: 'RollingUpdate' }, + }, + ], + }, + { + title: '标签与注解', + fields: [ + { + key: 'appLabel', + label: 'app 标签值', + type: 'text', + default: '', + tip: '留空则使用应用名称', + }, + { + key: 'versionLabel', + label: 'version 标签', + type: 'text', + placeholder: 'v1', + default: 'v1', + }, + { + key: 'imagePullPolicy', + label: '镜像拉取策略', + type: 'select', + options: [ + { label: 'IfNotPresent (推荐)', value: 'IfNotPresent' }, + { label: 'Always', value: 'Always' }, + { label: 'Never', value: 'Never' }, + ], + default: 'IfNotPresent', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sDeploymentYaml(config) { + const labels = { + app: config.appLabel || config.appName, + } + if (config.versionLabel) { + labels.version = config.versionLabel + } + + const container = { + name: config.appName, + image: config.image, + imagePullPolicy: config.imagePullPolicy, + ports: [{ + name: config.portName, + containerPort: config.containerPort, + protocol: config.protocol, + }], + resources: { + requests: { + cpu: config.cpuRequest, + memory: config.memoryRequest, + }, + limits: { + cpu: config.cpuLimit, + memory: config.memoryLimit, + }, + }, + } + + const probeConfig = (type, path, port) => { + if (type === 'httpGet') { + return { httpGet: { path, port, scheme: 'HTTP' } } + } + if (type === 'tcpSocket') { + return { tcpSocket: { port } } + } + return { exec: { command: ['cat', '/tmp/healthy'] } } + } + + if (config.enableLivenessProbe) { + container.livenessProbe = { + ...probeConfig(config.livenessProbeType, config.livenessPath, config.livenessPort), + initialDelaySeconds: 15, + periodSeconds: 20, + timeoutSeconds: 5, + failureThreshold: 3, + } + } + + if (config.enableReadinessProbe) { + container.readinessProbe = { + ...probeConfig(config.readinessProbeType, config.readinessPath, config.readinessPort), + initialDelaySeconds: 5, + periodSeconds: 10, + timeoutSeconds: 3, + failureThreshold: 3, + } + } + + const deployment = { + apiVersion: 'apps/v1', + kind: 'Deployment', + metadata: { + name: config.appName, + namespace: config.namespace, + labels, + }, + spec: { + replicas: config.replicas, + selector: { + matchLabels: { app: labels.app }, + }, + strategy: config.strategy === 'RollingUpdate' + ? { + type: 'RollingUpdate', + rollingUpdate: { + maxSurge: config.maxSurge, + maxUnavailable: config.maxUnavailable, + }, + } + : { type: 'Recreate' }, + template: { + metadata: { labels }, + spec: { + containers: [container], + }, + }, + }, + } + + const header = `# K8s Deployment - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'deployment.yaml'} +` + + return header + '\n' + toYaml(deployment) +} diff --git a/src/schemas/k8s-service.js b/src/schemas/k8s-service.js new file mode 100644 index 0000000..ef1c679 --- /dev/null +++ b/src/schemas/k8s-service.js @@ -0,0 +1,220 @@ +export const k8sServiceSchema = { + id: 'k8s-service', + name: 'K8s Service', + icon: 'Connection', + category: '云原生', + description: 'Kubernetes 服务发现与负载均衡', + format: 'yaml', + fileName: 'service.yaml', + groups: [ + { + title: '基础信息', + fields: [ + { + key: 'serviceName', + label: 'Service 名称', + type: 'text', + placeholder: 'my-app-svc', + default: 'my-app-svc', + required: true, + }, + { + key: 'namespace', + label: '命名空间', + type: 'text', + placeholder: 'default', + default: 'default', + }, + { + key: 'appLabel', + label: '关联应用标签 (Selector)', + type: 'text', + placeholder: 'my-app', + default: 'my-app', + required: true, + tip: '需匹配 Deployment 中 Pod 的 app 标签', + }, + ], + }, + { + title: 'Service 类型与端口', + fields: [ + { + key: 'serviceType', + label: 'Service 类型', + type: 'select', + options: [ + { label: 'ClusterIP - 集群内部访问', value: 'ClusterIP' }, + { label: 'NodePort - 节点端口暴露', value: 'NodePort' }, + { label: 'LoadBalancer - 云负载均衡', value: 'LoadBalancer' }, + ], + default: 'ClusterIP', + tip: 'ClusterIP: 仅集群内访问; NodePort: 通过节点IP+端口访问; LoadBalancer: 云厂商 LB', + }, + { + key: 'port', + label: 'Service 端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + tip: 'Service 暴露的端口', + }, + { + key: 'targetPort', + label: '目标容器端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + tip: '需与 Deployment 中容器端口一致', + }, + { + key: 'protocol', + label: '协议', + type: 'select', + options: [ + { label: 'TCP', value: 'TCP' }, + { label: 'UDP', value: 'UDP' }, + ], + default: 'TCP', + }, + { + key: 'portName', + label: '端口名称', + type: 'text', + placeholder: 'http', + default: 'http', + }, + { + key: 'nodePort', + label: 'NodePort 端口', + type: 'number', + min: 30000, + max: 32767, + default: 30080, + dependsOn: { key: 'serviceType', value: 'NodePort' }, + tip: 'NodePort 范围: 30000-32767', + }, + ], + }, + { + title: '会话保持与高级选项', + fields: [ + { + key: 'sessionAffinity', + label: '会话保持', + type: 'switch', + default: false, + tip: '启用后同一客户端请求会转发到同一 Pod', + }, + { + key: 'sessionTimeout', + label: '会话超时 (秒)', + type: 'number', + min: 1, + max: 86400, + default: 10800, + dependsOn: { key: 'sessionAffinity', value: true }, + }, + { + key: 'externalTrafficPolicy', + label: '外部流量策略', + type: 'select', + options: [ + { label: 'Cluster - 跨节点负载均衡 (默认)', value: 'Cluster' }, + { label: 'Local - 保留源 IP', value: 'Local' }, + ], + default: 'Cluster', + dependsOn: { key: 'serviceType', valueNotIn: ['ClusterIP'] }, + tip: 'Local 可保留客户端真实 IP', + }, + ], + }, + ], +} + +function toYaml(obj, indent = 0) { + const lines = [] + const prefix = ' '.repeat(indent) + + for (const [key, value] of Object.entries(obj)) { + if (value === null || value === undefined || value === '') continue + + if (Array.isArray(value)) { + lines.push(`${prefix}${key}:`) + for (const item of value) { + if (typeof item === 'object' && item !== null) { + const entries = Object.entries(item).filter(([, v]) => v !== null && v !== undefined && v !== '') + if (entries.length === 0) continue + const first = entries[0] + lines.push(`${prefix} - ${first[0]}: ${first[1]}`) + for (let i = 1; i < entries.length; i++) { + lines.push(`${prefix} ${entries[i][0]}: ${entries[i][1]}`) + } + } else { + lines.push(`${prefix} - ${item}`) + } + } + } else if (typeof value === 'object') { + lines.push(`${prefix}${key}:`) + lines.push(toYaml(value, indent + 1)) + } else { + lines.push(`${prefix}${key}: ${value}`) + } + } + + return lines.join('\n') +} + +export function generateK8sServiceYaml(config) { + const portSpec = { + name: config.portName, + port: config.port, + targetPort: config.targetPort, + protocol: config.protocol, + } + + if (config.serviceType === 'NodePort') { + portSpec.nodePort = config.nodePort + } + + const service = { + apiVersion: 'v1', + kind: 'Service', + metadata: { + name: config.serviceName, + namespace: config.namespace, + labels: { + app: config.appLabel, + }, + }, + spec: { + type: config.serviceType, + selector: { + app: config.appLabel, + }, + ports: [portSpec], + }, + } + + if (config.sessionAffinity) { + service.spec.sessionAffinity = 'ClientIP' + service.spec.sessionAffinityConfig = { + clientIP: { + timeoutSeconds: config.sessionTimeout, + }, + } + } + + if (config.serviceType !== 'ClusterIP' && config.externalTrafficPolicy) { + service.spec.externalTrafficPolicy = config.externalTrafficPolicy + } + + const header = `# K8s Service - 由 ConfTemplate 生成 +# 生成时间: ${new Date().toLocaleString('zh-CN')} +# 部署命令: kubectl apply -f ${config.fileName || 'service.yaml'} +` + + return header + '\n' + toYaml(service) +} diff --git a/src/schemas/mysql.js b/src/schemas/mysql.js new file mode 100644 index 0000000..b69b9d0 --- /dev/null +++ b/src/schemas/mysql.js @@ -0,0 +1,518 @@ +export const mysqlSchema = { + id: 'mysql', + name: 'MySQL', + icon: 'Coin', + category: '数据库', + description: '世界上最流行的开源关系型数据库', + format: 'cnf', + fileName: 'my.cnf', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'version', + label: 'MySQL 版本', + type: 'select', + options: [ + { label: 'MySQL 8.0 (推荐)', value: '8.0' }, + { label: 'MySQL 5.7', value: '5.7' }, + ], + default: '8.0', + }, + { + key: 'port', + label: '监听端口', + type: 'number', + min: 1, + max: 65535, + default: 3306, + }, + { + key: 'bindAddress', + label: '绑定地址', + type: 'text', + default: '0.0.0.0', + }, + { + key: 'datadir', + label: '数据目录', + type: 'text', + default: '/var/lib/mysql', + }, + { + key: 'socket', + label: 'Socket 文件路径', + type: 'text', + default: '/var/run/mysqld/mysqld.sock', + }, + { + key: 'characterSet', + label: '字符集', + type: 'select', + options: [ + { label: 'utf8mb4 (推荐,支持完整 Unicode)', value: 'utf8mb4' }, + { label: 'utf8', value: 'utf8' }, + { label: 'latin1', value: 'latin1' }, + ], + default: 'utf8mb4', + }, + { + key: 'collation', + label: '排序规则', + type: 'select', + options: [ + { label: 'utf8mb4_general_ci (通用,性能好)', value: 'utf8mb4_general_ci' }, + { label: 'utf8mb4_unicode_ci (Unicode 标准)', value: 'utf8mb4_unicode_ci' }, + { label: 'utf8mb4_0900_ai_ci (MySQL 8.0 默认)', value: 'utf8mb4_0900_ai_ci' }, + ], + default: 'utf8mb4_general_ci', + }, + { + key: 'timezone', + label: '时区', + type: 'select', + options: [ + { label: '+08:00 (中国标准时间)', value: '+08:00' }, + { label: 'SYSTEM (跟随系统)', value: 'SYSTEM' }, + { label: '+00:00 (UTC)', value: '+00:00' }, + ], + default: '+08:00', + }, + ], + }, + { + title: '内存与性能', + fields: [ + { + key: 'serverMemory', + label: '服务器总内存', + type: 'select', + options: [ + { label: '2GB', value: '2' }, + { label: '4GB', value: '4' }, + { label: '8GB', value: '8' }, + { label: '16GB', value: '16' }, + { label: '32GB', value: '32' }, + { label: '64GB', value: '64' }, + ], + default: '8', + tip: '用于自动计算 Buffer Pool 等参数', + }, + { + key: 'maxConnections', + label: '最大连接数', + type: 'number', + min: 10, + max: 10000, + default: 500, + }, + { + key: 'innodbBufferPoolSize', + label: 'InnoDB Buffer Pool 大小', + type: 'select', + options: [ + { label: '自动计算 (推荐)', value: 'auto' }, + { label: '256M', value: '256M' }, + { label: '512M', value: '512M' }, + { label: '1G', value: '1G' }, + { label: '2G', value: '2G' }, + { label: '4G', value: '4G' }, + { label: '8G', value: '8G' }, + { label: '16G', value: '16G' }, + ], + default: 'auto', + tip: '通常设为服务器内存的 60-80%', + }, + { + key: 'innodbLogFileSize', + label: 'Redo Log 文件大小', + type: 'select', + options: [ + { label: '256M', value: '256M' }, + { label: '512M', value: '512M' }, + { label: '1G', value: '1G' }, + ], + default: '256M', + }, + { + key: 'innodbFlushLogAtTrxCommit', + label: '事务日志刷盘策略', + type: 'select', + options: [ + { label: '1 - 每次事务提交都刷盘 (最安全)', value: '1' }, + { label: '2 - 每秒刷盘 (性能与安全平衡)', value: '2' }, + { label: '0 - 由操作系统决定 (性能最高)', value: '0' }, + ], + default: '1', + }, + { + key: 'innodbIoCapacity', + label: 'IO 容量 (IOPS)', + type: 'select', + options: [ + { label: '200 (HDD 机械硬盘)', value: '200' }, + { label: '1000 (普通 SSD)', value: '1000' }, + { label: '2000 (高性能 SSD)', value: '2000' }, + { label: '10000 (NVMe SSD)', value: '10000' }, + ], + default: '2000', + }, + ], + }, + { + title: '查询优化', + fields: [ + { + key: 'queryCacheType', + label: '查询缓存 (仅 5.7)', + type: 'select', + options: [ + { label: '关闭 (MySQL 8.0 已移除)', value: '0' }, + { label: '按需开启', value: '1' }, + { label: '全部开启', value: '2' }, + ], + default: '0', + dependsOn: { key: 'version', value: '5.7' }, + }, + { + key: 'tmpTableSize', + label: '临时表最大大小', + type: 'select', + options: [ + { label: '16M', value: '16M' }, + { label: '32M', value: '32M' }, + { label: '64M', value: '64M' }, + { label: '128M', value: '128M' }, + ], + default: '64M', + }, + { + key: 'maxHeapTableSize', + label: '内存临时表最大大小', + type: 'select', + options: [ + { label: '16M', value: '16M' }, + { label: '32M', value: '32M' }, + { label: '64M', value: '64M' }, + { label: '128M', value: '128M' }, + ], + default: '64M', + }, + { + key: 'sortBufferSize', + label: '排序缓冲区大小', + type: 'select', + options: [ + { label: '256K', value: '256K' }, + { label: '512K', value: '512K' }, + { label: '1M', value: '1M' }, + { label: '2M', value: '2M' }, + { label: '4M', value: '4M' }, + ], + default: '1M', + }, + { + key: 'joinBufferSize', + label: 'JOIN 缓冲区大小', + type: 'select', + options: [ + { label: '256K', value: '256K' }, + { label: '512K', value: '512K' }, + { label: '1M', value: '1M' }, + { label: '2M', value: '2M' }, + ], + default: '512K', + }, + ], + }, + { + title: '日志配置', + fields: [ + { + key: 'enableSlowQueryLog', + label: '开启慢查询日志', + type: 'switch', + default: true, + }, + { + key: 'slowQueryLogTime', + label: '慢查询阈值 (秒)', + type: 'number', + min: 0, + max: 60, + default: 2, + dependsOn: { key: 'enableSlowQueryLog', value: true }, + tip: '超过该时间的 SQL 会被记录', + }, + { + key: 'enableGeneralLog', + label: '开启通用查询日志', + type: 'switch', + default: false, + tip: '记录所有 SQL,生产环境不建议开启', + }, + { + key: 'enableBinlog', + label: '开启 Binlog', + type: 'switch', + default: true, + tip: '用于主从复制和数据恢复', + }, + { + key: 'binlogFormat', + label: 'Binlog 格式', + type: 'select', + options: [ + { label: 'ROW (推荐,数据一致性最好)', value: 'ROW' }, + { label: 'STATEMENT (记录 SQL 语句)', value: 'STATEMENT' }, + { label: 'MIXED (混合模式)', value: 'MIXED' }, + ], + default: 'ROW', + dependsOn: { key: 'enableBinlog', value: true }, + }, + { + key: 'binlogExpireDays', + label: 'Binlog 过期天数', + type: 'number', + min: 1, + max: 365, + default: 15, + dependsOn: { key: 'enableBinlog', value: true }, + }, + ], + }, + { + title: '主从复制', + fields: [ + { + key: 'enableReplication', + label: '开启主从复制', + type: 'switch', + default: false, + }, + { + key: 'serverId', + label: 'Server ID', + type: 'number', + min: 1, + max: 4294967295, + default: 1, + dependsOn: { key: 'enableReplication', value: true }, + tip: '每个 MySQL 实例必须唯一', + required: true, + }, + { + key: 'replicationRole', + label: '角色', + type: 'select', + options: [ + { label: 'Master (主库)', value: 'master' }, + { label: 'Slave (从库)', value: 'slave' }, + ], + default: 'master', + dependsOn: { key: 'enableReplication', value: true }, + }, + { + key: 'masterHost', + label: '主库地址', + type: 'text', + placeholder: '192.168.1.100', + default: '', + dependsOn: { key: 'replicationRole', value: 'slave' }, + required: true, + }, + { + key: 'masterPort', + label: '主库端口', + type: 'number', + min: 1, + max: 65535, + default: 3306, + dependsOn: { key: 'replicationRole', value: 'slave' }, + }, + { + key: 'replicateDoDb', + label: '复制的数据库', + type: 'text', + placeholder: 'mydb (留空复制所有)', + default: '', + dependsOn: { key: 'enableReplication', value: true }, + }, + ], + }, + ], +} + +function calculateBufferPoolSize(serverMemory) { + const mem = parseInt(serverMemory) + if (mem <= 2) return '512M' + if (mem <= 4) return '2G' + if (mem <= 8) return '5G' + if (mem <= 16) return '10G' + if (mem <= 32) return '20G' + return '40G' +} + +export function generateMysqlConf(config) { + const lines = [] + lines.push(`# MySQL 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# MySQL ${config.version} | 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + const bufferSize = config.innodbBufferPoolSize === 'auto' + ? calculateBufferPoolSize(config.serverMemory) + : config.innodbBufferPoolSize + + lines.push(`[mysqld]`) + lines.push(``) + + // 基础 + lines.push(`# ======================== 基础配置 ========================`) + lines.push(`port = ${config.port}`) + lines.push(`bind-address = ${config.bindAddress}`) + lines.push(`datadir = ${config.datadir}`) + lines.push(`socket = ${config.socket}`) + lines.push(`pid-file = /var/run/mysqld/mysqld.pid`) + lines.push(`default-storage-engine = InnoDB`) + lines.push(`character-set-server = ${config.characterSet}`) + lines.push(`collation-server = ${config.collation}`) + lines.push(`default-time-zone = '${config.timezone}'`) + lines.push(`lower_case_table_names = 1`) + lines.push(`skip-name-resolve`) + lines.push(`skip-external-locking`) + + if (config.enableReplication) { + lines.push(`server-id = ${config.serverId}`) + } + + lines.push(``) + + // 连接 + lines.push(`# ======================== 连接配置 ========================`) + lines.push(`max_connections = ${config.maxConnections}`) + lines.push(`max_connect_errors = 100000`) + lines.push(`wait_timeout = 600`) + lines.push(`interactive_timeout = 600`) + lines.push(`connect_timeout = 10`) + lines.push(`max_allowed_packet = 64M`) + lines.push(`back_log = 256`) + lines.push(``) + + // 内存与性能 + lines.push(`# ======================== 内存与性能 ========================`) + lines.push(`innodb_buffer_pool_size = ${bufferSize}`) + lines.push(`innodb_buffer_pool_instances = ${parseInt(bufferSize) >= 1 ? Math.min(parseInt(bufferSize), 8) : 1}`) + lines.push(`innodb_log_file_size = ${config.innodbLogFileSize}`) + lines.push(`innodb_log_buffer_size = 64M`) + lines.push(`innodb_flush_log_at_trx_commit = ${config.innodbFlushLogAtTrxCommit}`) + lines.push(`innodb_io_capacity = ${config.innodbIoCapacity}`) + lines.push(`innodb_io_capacity_max = ${parseInt(config.innodbIoCapacity) * 2}`) + lines.push(`innodb_flush_method = O_DIRECT`) + lines.push(`innodb_file_per_table = 1`) + lines.push(`innodb_open_files = 65535`) + lines.push(`innodb_read_io_threads = 4`) + lines.push(`innodb_write_io_threads = 4`) + lines.push(`innodb_purge_threads = 4`) + lines.push(`innodb_thread_concurrency = 0`) + lines.push(``) + + // 查询优化 + lines.push(`# ======================== 查询优化 ========================`) + lines.push(`tmp_table_size = ${config.tmpTableSize}`) + lines.push(`max_heap_table_size = ${config.maxHeapTableSize}`) + lines.push(`sort_buffer_size = ${config.sortBufferSize}`) + lines.push(`join_buffer_size = ${config.joinBufferSize}`) + lines.push(`read_buffer_size = 256K`) + lines.push(`read_rnd_buffer_size = 512K`) + lines.push(`thread_cache_size = 64`) + lines.push(`table_open_cache = 4096`) + lines.push(`table_definition_cache = 2048`) + lines.push(``) + + if (config.version === '5.7' && config.queryCacheType !== '0') { + lines.push(`query_cache_type = ${config.queryCacheType}`) + lines.push(`query_cache_size = 64M`) + lines.push(``) + } + + // 日志 + lines.push(`# ======================== 日志配置 ========================`) + + if (config.enableSlowQueryLog) { + lines.push(`slow_query_log = 1`) + lines.push(`slow_query_log_file = /var/log/mysql/slow.log`) + lines.push(`long_query_time = ${config.slowQueryLogTime}`) + lines.push(`log_queries_not_using_indexes = 1`) + lines.push(`min_examined_row_limit = 100`) + } else { + lines.push(`slow_query_log = 0`) + } + + if (config.enableGeneralLog) { + lines.push(`general_log = 1`) + lines.push(`general_log_file = /var/log/mysql/general.log`) + } + + lines.push(`log-error = /var/log/mysql/error.log`) + + if (config.enableBinlog) { + lines.push(`log_bin = /var/log/mysql/mysql-bin`) + lines.push(`binlog_format = ${config.binlogFormat}`) + lines.push(`expire_logs_days = ${config.binlogExpireDays}`) + lines.push(`max_binlog_size = 256M`) + lines.push(`binlog_cache_size = 4M`) + lines.push(`sync_binlog = 1`) + } + lines.push(``) + + // 主从复制 + if (config.enableReplication) { + lines.push(`# ======================== 主从复制 ========================`) + + if (config.replicationRole === 'master') { + lines.push(`# Master 配置`) + lines.push(`binlog_do_db = ${config.replicateDoDb || '(所有数据库)'}`) + lines.push(``) + lines.push(`# 在主库创建复制用户:`) + lines.push(`# CREATE USER 'repl'@'%' IDENTIFIED BY 'strong_password';`) + lines.push(`# GRANT REPLICATION SLAVE ON *.* TO 'repl'@'%';`) + lines.push(`# FLUSH PRIVILEGES;`) + } else { + lines.push(`# Slave 配置`) + lines.push(`relay-log = /var/log/mysql/relay-bin`) + lines.push(`read_only = 1`) + lines.push(`super_read_only = 1`) + lines.push(`log_slave_updates = 1`) + if (config.replicateDoDb) { + lines.push(`replicate-do-db = ${config.replicateDoDb}`) + } + lines.push(``) + lines.push(`# 在从库执行以下命令配置主库信息:`) + lines.push(`# CHANGE MASTER TO`) + lines.push(`# MASTER_HOST='${config.masterHost || 'MASTER_IP'}',`) + lines.push(`# MASTER_PORT=${config.masterPort},`) + lines.push(`# MASTER_USER='repl',`) + lines.push(`# MASTER_PASSWORD='strong_password',`) + lines.push(`# MASTER_AUTO_POSITION=1;`) + lines.push(`# START SLAVE;`) + } + lines.push(``) + } + + // 安全 + lines.push(`# ======================== 安全配置 ========================`) + lines.push(`sql_mode = STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION`) + + if (config.version === '8.0') { + lines.push(`authentication_policy = mysql_native_password`) + } + + lines.push(``) + lines.push(`[client]`) + lines.push(`port = ${config.port}`) + lines.push(`socket = ${config.socket}`) + lines.push(`default-character-set = ${config.characterSet}`) + + return lines.join('\n') +} diff --git a/src/schemas/nginx.js b/src/schemas/nginx.js new file mode 100644 index 0000000..3a0edb1 --- /dev/null +++ b/src/schemas/nginx.js @@ -0,0 +1,339 @@ +export const nginxSchema = { + id: 'nginx', + name: 'NGINX', + icon: 'Monitor', + category: '代理与Web', + description: '高性能 HTTP 和反向代理服务器', + format: 'conf', + fileName: 'nginx.conf', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'workerProcesses', + label: 'Worker 进程数', + type: 'select', + options: [ + { label: 'auto (自动检测)', value: 'auto' }, + { label: '1', value: '1' }, + { label: '2', value: '2' }, + { label: '4', value: '4' }, + { label: '8', value: '8' }, + ], + default: 'auto', + tip: '通常设置为 CPU 核心数', + }, + { + key: 'workerConnections', + label: '单 Worker 最大连接数', + type: 'number', + min: 512, + max: 65535, + step: 512, + default: 1024, + tip: '每个 Worker 进程能处理的最大并发连接数', + }, + ], + }, + { + title: 'HTTP 服务', + fields: [ + { + key: 'httpPort', + label: 'HTTP 端口', + type: 'number', + min: 1, + max: 65535, + default: 80, + }, + { + key: 'serverName', + label: '域名 (server_name)', + type: 'text', + placeholder: 'example.com', + default: 'localhost', + required: true, + tip: '支持通配符,如 *.example.com', + }, + { + key: 'enableGzip', + label: '开启 Gzip 压缩', + type: 'switch', + default: true, + tip: '压缩响应体,减少传输体积', + }, + { + key: 'gzipTypes', + label: 'Gzip 压缩类型', + type: 'text', + default: 'text/plain text/css application/json application/javascript text/xml', + dependsOn: { key: 'enableGzip', value: true }, + tip: 'MIME 类型,空格分隔', + }, + ], + }, + { + title: 'HTTPS / SSL', + fields: [ + { + key: 'enableHttps', + label: '开启 HTTPS', + type: 'switch', + default: false, + }, + { + key: 'httpsPort', + label: 'HTTPS 端口', + type: 'number', + min: 1, + max: 65535, + default: 443, + dependsOn: { key: 'enableHttps', value: true }, + }, + { + key: 'sslCertificate', + label: 'SSL 证书路径', + type: 'text', + placeholder: '/etc/nginx/ssl/cert.pem', + default: '/etc/nginx/ssl/cert.pem', + dependsOn: { key: 'enableHttps', value: true }, + required: true, + }, + { + key: 'sslCertificateKey', + label: 'SSL 私钥路径', + type: 'text', + placeholder: '/etc/nginx/ssl/key.pem', + default: '/etc/nginx/ssl/key.pem', + dependsOn: { key: 'enableHttps', value: true }, + required: true, + }, + { + key: 'sslProtocols', + label: 'TLS 版本', + type: 'select', + options: [ + { label: 'TLSv1.2 + TLSv1.3 (推荐)', value: 'TLSv1.2 TLSv1.3' }, + { label: 'TLSv1.1 + TLSv1.2 + TLSv1.3', value: 'TLSv1.1 TLSv1.2 TLSv1.3' }, + { label: '仅 TLSv1.3', value: 'TLSv1.3' }, + ], + default: 'TLSv1.2 TLSv1.3', + dependsOn: { key: 'enableHttps', value: true }, + }, + ], + }, + { + title: '反向代理', + fields: [ + { + key: 'enableProxy', + label: '开启反向代理', + type: 'switch', + default: false, + tip: '将请求转发到后端应用服务器', + }, + { + key: 'proxyTarget', + label: '后端地址', + type: 'text', + placeholder: 'http://127.0.0.1:8080', + default: 'http://127.0.0.1:8080', + dependsOn: { key: 'enableProxy', value: true }, + required: true, + }, + { + key: 'proxyPath', + label: '代理路径前缀', + type: 'text', + placeholder: '/', + default: '/', + dependsOn: { key: 'enableProxy', value: true }, + }, + ], + }, + { + title: '高级选项', + fields: [ + { + key: 'enableCors', + label: '开启跨域 (CORS)', + type: 'switch', + default: false, + tip: '添加跨域资源共享响应头', + }, + { + key: 'clientMaxBodySize', + label: '请求体最大限制', + type: 'select', + options: [ + { label: '1m', value: '1m' }, + { label: '10m', value: '10m' }, + { label: '50m', value: '50m' }, + { label: '100m', value: '100m' }, + { label: '1g', value: '1g' }, + ], + default: '10m', + tip: '文件上传大小限制', + }, + { + key: 'accessLog', + label: '开启访问日志', + type: 'switch', + default: true, + }, + { + key: 'keepaliveTimeout', + label: 'Keep-Alive 超时 (秒)', + type: 'number', + min: 5, + max: 300, + default: 65, + }, + ], + }, + ], +} + +export function generateNginxConf(config) { + const lines = [] + lines.push(`# NGINX 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + lines.push(`user nginx;`) + lines.push(`worker_processes ${config.workerProcesses};`) + lines.push(`error_log /var/log/nginx/error.log warn;`) + lines.push(`pid /var/run/nginx.pid;`) + lines.push(``) + lines.push(`events {`) + lines.push(` worker_connections ${config.workerConnections};`) + lines.push(`}`) + lines.push(``) + lines.push(`http {`) + lines.push(` include /etc/nginx/mime.types;`) + lines.push(` default_type application/octet-stream;`) + lines.push(``) + + if (config.accessLog) { + lines.push(` log_format main '$remote_addr - $remote_user [$time_local] "$request" '`) + lines.push(` '$status $body_bytes_sent "$http_referer" '`) + lines.push(` '"$http_user_agent" "$http_x_forwarded_for"';`) + lines.push(``) + lines.push(` access_log /var/log/nginx/access.log main;`) + lines.push(``) + } + + lines.push(` sendfile on;`) + lines.push(` tcp_nopush on;`) + lines.push(` tcp_nodelay on;`) + lines.push(` keepalive_timeout ${config.keepaliveTimeout};`) + lines.push(` types_hash_max_size 2048;`) + lines.push(` client_max_body_size ${config.clientMaxBodySize};`) + lines.push(``) + + if (config.enableGzip) { + lines.push(` # Gzip 压缩配置`) + lines.push(` gzip on;`) + lines.push(` gzip_vary on;`) + lines.push(` gzip_proxied any;`) + lines.push(` gzip_comp_level 6;`) + lines.push(` gzip_buffers 16 8k;`) + lines.push(` gzip_http_version 1.1;`) + lines.push(` gzip_min_length 256;`) + lines.push(` gzip_types ${config.gzipTypes};`) + lines.push(``) + } + + lines.push(` # 上游服务器${config.enableProxy ? '' : ' (示例,未启用)'}`) + lines.push(` # upstream backend {`) + lines.push(` # server 127.0.0.1:8080;`) + lines.push(` # }`) + lines.push(``) + + lines.push(` server {`) + lines.push(` listen ${config.httpPort};`) + + if (config.enableHttps) { + lines.push(` listen ${config.httpsPort} ssl http2;`) + } + + lines.push(` server_name ${config.serverName};`) + lines.push(``) + + if (config.enableHttps) { + lines.push(` # SSL 证书配置`) + lines.push(` ssl_certificate ${config.sslCertificate};`) + lines.push(` ssl_certificate_key ${config.sslCertificateKey};`) + lines.push(` ssl_protocols ${config.sslProtocols};`) + lines.push(` ssl_ciphers HIGH:!aNULL:!MD5;`) + lines.push(` ssl_prefer_server_ciphers on;`) + lines.push(` ssl_session_cache shared:SSL:10m;`) + lines.push(` ssl_session_timeout 10m;`) + lines.push(``) + + // HTTP to HTTPS redirect + lines.push(` # HTTP 自动跳转 HTTPS`) + lines.push(` }`) + lines.push(``) + lines.push(` server {`) + lines.push(` listen 80;`) + lines.push(` server_name ${config.serverName};`) + lines.push(` return 301 https://$host$request_uri;`) + lines.push(` }`) + lines.push(``) + lines.push(` server {`) + lines.push(` listen ${config.httpsPort} ssl http2;`) + lines.push(` server_name ${config.serverName};`) + lines.push(` ssl_certificate ${config.sslCertificate};`) + lines.push(` ssl_certificate_key ${config.sslCertificateKey};`) + lines.push(``) + } + + if (config.enableCors) { + lines.push(` # 跨域配置 (CORS)`) + lines.push(` add_header 'Access-Control-Allow-Origin' '*' always;`) + lines.push(` add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;`) + lines.push(` add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization' always;`) + lines.push(` add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;`) + lines.push(``) + lines.push(` if ($request_method = 'OPTIONS') {`) + lines.push(` add_header 'Access-Control-Allow-Origin' '*';`) + lines.push(` add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS';`) + lines.push(` add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization';`) + lines.push(` add_header 'Access-Control-Max-Age' 1728000;`) + lines.push(` add_header 'Content-Type' 'text/plain; charset=utf-8';`) + lines.push(` add_header 'Content-Length' 0;`) + lines.push(` return 204;`) + lines.push(` }`) + lines.push(``) + } + + lines.push(` location / {`) + + if (config.enableProxy) { + lines.push(` proxy_pass ${config.proxyTarget};`) + lines.push(` proxy_http_version 1.1;`) + lines.push(` proxy_set_header Upgrade $http_upgrade;`) + lines.push(` proxy_set_header Connection 'upgrade';`) + lines.push(` proxy_set_header Host $host;`) + lines.push(` proxy_set_header X-Real-IP $remote_addr;`) + lines.push(` proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;`) + lines.push(` proxy_set_header X-Forwarded-Proto $scheme;`) + lines.push(` proxy_cache_bypass $http_upgrade;`) + } else { + lines.push(` root /usr/share/nginx/html;`) + lines.push(` index index.html index.htm;`) + } + + lines.push(` }`) + lines.push(``) + lines.push(` error_page 404 /404.html;`) + lines.push(` error_page 500 502 503 504 /50x.html;`) + lines.push(` location = /50x.html {`) + lines.push(` root /usr/share/nginx/html;`) + lines.push(` }`) + lines.push(` }`) + lines.push(`}`) + + return lines.join('\n') +} diff --git a/src/schemas/redis.js b/src/schemas/redis.js new file mode 100644 index 0000000..d3a3773 --- /dev/null +++ b/src/schemas/redis.js @@ -0,0 +1,319 @@ +export const redisSchema = { + id: 'redis', + name: 'Redis', + icon: 'Coin', + category: '缓存', + description: '高性能内存键值存储与缓存', + format: 'conf', + fileName: 'redis.conf', + groups: [ + { + title: '基础配置', + fields: [ + { + key: 'port', + label: '监听端口', + type: 'number', + min: 1, + max: 65535, + default: 6379, + }, + { + key: 'bind', + label: '绑定地址', + type: 'text', + default: '0.0.0.0', + tip: '0.0.0.0 允许所有地址连接,生产环境建议限制', + }, + { + key: 'daemonize', + label: '守护进程模式', + type: 'switch', + default: true, + tip: '后台运行 Redis', + }, + { + key: 'requirepass', + label: '访问密码', + type: 'text', + placeholder: '留空表示无密码', + default: '', + tip: '生产环境务必设置强密码', + }, + ], + }, + { + title: '内存管理', + fields: [ + { + key: 'maxmemory', + label: '最大内存限制', + type: 'select', + options: [ + { label: '128mb', value: '128mb' }, + { label: '256mb', value: '256mb' }, + { label: '512mb', value: '512mb' }, + { label: '1gb', value: '1gb' }, + { label: '2gb', value: '2gb' }, + { label: '4gb', value: '4gb' }, + { label: '8gb', value: '8gb' }, + { label: '不限制', value: '0' }, + ], + default: '256mb', + tip: 'Redis 最大可用内存', + }, + { + key: 'maxmemoryPolicy', + label: '内存淘汰策略', + type: 'select', + options: [ + { label: 'noeviction - 不淘汰,内存满时写入报错', value: 'noeviction' }, + { label: 'allkeys-lru - 所有键中淘汰最近最少使用 (推荐)', value: 'allkeys-lru' }, + { label: 'volatile-lru - 仅从设置了过期时间的键中淘汰 LRU', value: 'volatile-lru' }, + { label: 'allkeys-random - 所有键中随机淘汰', value: 'allkeys-random' }, + { label: 'volatile-ttl - 淘汰最近将过期的键', value: 'volatile-ttl' }, + ], + default: 'allkeys-lru', + dependsOn: { key: 'maxmemory', valueNotIn: ['0'] }, + }, + ], + }, + { + title: '持久化配置', + fields: [ + { + key: 'enableRdb', + label: 'RDB 快照', + type: 'switch', + default: true, + tip: '定期将内存数据快照写入磁盘', + }, + { + key: 'rdbSaveRules', + label: 'RDB 保存规则', + type: 'select', + options: [ + { label: '默认策略 (推荐)', value: 'default' }, + { label: '保守策略 (数据安全优先)', value: 'conservative' }, + { label: '激进策略 (性能优先)', value: 'aggressive' }, + ], + default: 'default', + dependsOn: { key: 'enableRdb', value: true }, + }, + { + key: 'enableAof', + label: 'AOF 持久化', + type: 'switch', + default: false, + tip: '记录每次写操作,数据安全性更高', + }, + { + key: 'aofFsync', + label: 'AOF 同步策略', + type: 'select', + options: [ + { label: 'everysec - 每秒同步 (推荐)', value: 'everysec' }, + { label: 'always - 每次写入同步 (最安全,性能最低)', value: 'always' }, + { label: 'no - 由操作系统决定 (性能最高)', value: 'no' }, + ], + default: 'everysec', + dependsOn: { key: 'enableAof', value: true }, + }, + ], + }, + { + title: '运行模式', + fields: [ + { + key: 'mode', + label: '运行模式', + type: 'select', + options: [ + { label: '单机模式 (Standalone)', value: 'standalone' }, + { label: '哨兵模式 (Sentinel)', value: 'sentinel' }, + { label: '集群模式 (Cluster)', value: 'cluster' }, + ], + default: 'standalone', + }, + { + key: 'sentinelPort', + label: '哨兵端口', + type: 'number', + min: 1, + max: 65535, + default: 26379, + dependsOn: { key: 'mode', value: 'sentinel' }, + }, + { + key: 'sentinelMasterName', + label: '主节点名称', + type: 'text', + default: 'mymaster', + dependsOn: { key: 'mode', value: 'sentinel' }, + }, + { + key: 'sentinelDownAfter', + label: '主观下线时间 (毫秒)', + type: 'number', + min: 1000, + max: 60000, + step: 1000, + default: 5000, + dependsOn: { key: 'mode', value: 'sentinel' }, + }, + { + key: 'clusterNodeTimeout', + label: '集群节点超时 (毫秒)', + type: 'number', + min: 1000, + max: 30000, + step: 1000, + default: 5000, + dependsOn: { key: 'mode', value: 'cluster' }, + }, + ], + }, + { + title: '连接与性能', + fields: [ + { + key: 'timeout', + label: '空闲连接超时 (秒)', + type: 'number', + min: 0, + max: 3600, + default: 300, + tip: '0 表示不超时', + }, + { + key: 'maxclients', + label: '最大客户端连接数', + type: 'number', + min: 1, + max: 100000, + default: 10000, + }, + { + key: 'tcpKeepalive', + label: 'TCP Keep-Alive (秒)', + type: 'number', + min: 0, + max: 3600, + default: 300, + tip: '0 表示不开启', + }, + ], + }, + ], +} + +export function generateRedisConf(config) { + const lines = [] + lines.push(`# Redis 配置文件 - 由 ConfTemplate 生成`) + lines.push(`# 生成时间: ${new Date().toLocaleString('zh-CN')}`) + lines.push(``) + + // 基础配置 + lines.push(`################################## 基础配置 ##################################`) + lines.push(``) + lines.push(`port ${config.port}`) + lines.push(`bind ${config.bind}`) + lines.push(`daemonize ${config.daemonize ? 'yes' : 'no'}`) + lines.push(`pidfile /var/run/redis_${config.port}.pid`) + lines.push(`loglevel notice`) + lines.push(`logfile /var/log/redis/redis_${config.port}.log`) + lines.push(`databases 16`) + + if (config.requirepass) { + lines.push(``) + lines.push(`# 安全 - 访问密码`) + lines.push(`requirepass ${config.requirepass}`) + } + lines.push(``) + + // 内存管理 + lines.push(`################################## 内存管理 ##################################`) + lines.push(``) + if (config.maxmemory === '0') { + lines.push(`# maxmemory # 未设置内存限制`) + } else { + lines.push(`maxmemory ${config.maxmemory}`) + lines.push(`maxmemory-policy ${config.maxmemoryPolicy}`) + } + lines.push(``) + + // 持久化 + lines.push(`################################## 持久化 ##################################`) + lines.push(``) + + if (config.enableRdb) { + lines.push(`# RDB 快照配置`) + lines.push(`dbfilename dump.rdb`) + lines.push(`dir /var/lib/redis`) + + if (config.rdbSaveRules === 'default') { + lines.push(`save 900 1`) + lines.push(`save 300 10`) + lines.push(`save 60 10000`) + } else if (config.rdbSaveRules === 'conservative') { + lines.push(`save 3600 1`) + lines.push(`save 300 100`) + lines.push(`save 60 10000`) + } else { + lines.push(`save 60 1000`) + lines.push(`save 10 10000`) + } + lines.push(`rdbcompression yes`) + lines.push(`rdbchecksum yes`) + } else { + lines.push(`# RDB 快照已禁用`) + lines.push(`save ""`) + } + lines.push(``) + + if (config.enableAof) { + lines.push(`# AOF 持久化配置`) + lines.push(`appendonly yes`) + lines.push(`appendfilename "appendonly.aof"`) + lines.push(`appendfsync ${config.aofFsync}`) + lines.push(`auto-aof-rewrite-percentage 100`) + lines.push(`auto-aof-rewrite-min-size 64mb`) + } else { + lines.push(`# AOF 持久化已禁用`) + lines.push(`appendonly no`) + } + lines.push(``) + + // 运行模式 + if (config.mode === 'sentinel') { + lines.push(`################################## 哨兵配置 ##################################`) + lines.push(`# 以下为 sentinel.conf 内容,需单独放置`) + lines.push(``) + lines.push(`port ${config.sentinelPort}`) + lines.push(`sentinel monitor ${config.sentinelMasterName} 127.0.0.1 ${config.port} 2`) + lines.push(`sentinel down-after-milliseconds ${config.sentinelMasterName} ${config.sentinelDownAfter}`) + lines.push(`sentinel failover-timeout ${config.sentinelMasterName} 60000`) + lines.push(`sentinel parallel-syncs ${config.sentinelMasterName} 1`) + lines.push(``) + } else if (config.mode === 'cluster') { + lines.push(`################################## 集群配置 ##################################`) + lines.push(``) + lines.push(`cluster-enabled yes`) + lines.push(`cluster-config-file nodes-${config.port}.conf`) + lines.push(`cluster-node-timeout ${config.clusterNodeTimeout}`) + lines.push(`cluster-announce-ip 127.0.0.1`) + lines.push(`cluster-announce-port ${config.port}`) + lines.push(`cluster-announce-bus-port ${config.port + 10000}`) + lines.push(``) + } + + // 连接与性能 + lines.push(`################################## 连接与性能 ##################################`) + lines.push(``) + lines.push(`timeout ${config.timeout}`) + lines.push(`tcp-keepalive ${config.tcpKeepalive}`) + lines.push(`maxclients ${config.maxclients}`) + lines.push(`tcp-backlog 511`) + + return lines.join('\n') +} diff --git a/vite.config.js b/vite.config.js new file mode 100644 index 0000000..f8551ef --- /dev/null +++ b/vite.config.js @@ -0,0 +1,21 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import AutoImport from 'unplugin-auto-import/vite' +import Components from 'unplugin-vue-components/vite' +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' + +export default defineConfig({ + plugins: [ + vue(), + AutoImport({ + resolvers: [ElementPlusResolver()], + }), + Components({ + resolvers: [ElementPlusResolver()], + }), + ], + server: { + host: '0.0.0.0', + port: 3000, + }, +})