commit 60931e2c21a628d701ca43ba56987a43bba624b7 Author: 湛兮 Date: Thu Jun 11 20:49:59 2026 +0800 feat: 初始化DevOps平台前端 diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..646b153 --- /dev/null +++ b/.env.example @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=http://localhost:4300 +VITE_DEVOPS_USE_MOCK=false diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..a7cea0b --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar"] +} diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..ebd7d9b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,33 @@ +@RTK.md + +# DevOps 运维平台前端 Agent 入口 + +## 项目规则 + +- 使用当前 Vue 3 + Vite + TypeScript 结构,不引入无关框架。 +- 页面内容、计划文档和交付说明优先使用中文。 +- UI 风格保持安静、工具型、信息密度合理的运维后台,不做营销页。 +- 只在 `devops-platform-web/**` 内修改前端文件,不修改 `devops-platform-api/**` 和 `devops-platform-docs/**`。 +- 改动可运行入口、目录或重要文件时,同步更新 `README.md`。 + +## 技术约束 + +- UI 组件优先使用 Element Plus。 +- 状态管理使用 Pinia。 +- 路由使用 Vue Router。 +- 请求封装使用 Axios,并集中在 `src/services/**`。 +- BPMN 展示使用 `bpmn-js`,不手写替代渲染器。 +- mock 数据集中在 `src/mocks/**`,后续以后端 OpenAPI/Swagger 替换。 + +## API 与安全边界 + +- 前端不得保存 Jenkins token、Gitea token、通知 webhook、LLM API key/baseURL/model。 +- 前端不得直接调用 Jenkins、Gitea、企微、飞书或模型供应商;统一通过后端 API。 +- API 契约以 `/Users/mac033/Desktop/my-project/devops-platform-docs/devops-platform-implementation-tracker.md` 和后端 OpenAPI 为准。 + +## BPMN 与 Agent 边界 + +- BPMN 组件只展示流程图、节点状态和节点详情,不推进状态机。 +- `step.bpmnNodeId` 必须与 BPMN XML 节点 id 一致。 +- Agent 只能作为全局右侧抽屉工具存在,不作为独立侧边栏路由;能力只允许 DevOps 场景:发布风险摘要、失败诊断、Runbook 问答、发布说明草稿。 +- 禁止在前端新增泛聊天入口。 diff --git a/README.md b/README.md new file mode 100644 index 0000000..393d9c4 --- /dev/null +++ b/README.md @@ -0,0 +1,66 @@ +# DevOps 运维平台前端 + +Vue 3 + Vite + TypeScript 实现的 DevOps 运维控制台第一阶段,用于管理本地项目和 DevOps 平台自身的发布状态、Jenkins/Gitea/通知平台/LLM 接入状态、BPMN 流程展示和 Agent 辅助能力。 + +## 当前范围 + +- 登录、总览、项目配置、发布中心、运行记录、系统配置,以及全局右侧 Agent 抽屉。 +- 优先调用 `devops-platform-api`,后端不可用时降级到 mock 数据展示 `access-manage`、`role-admin`、`role-user`、`my-resume`、`devops-platform-api`、`devops-platform-web`。 +- 发布中心包含项目、环境、ref/tag、备注、生产二次确认、触发、取消、重试和 Jenkins 状态同步入口。 +- 运行记录展示 Jenkins queue/build、Build URL、开始/结束时间、执行提示、失败摘要和日志摘要。 +- BPMN Viewer 使用 `bpmn-js` 渲染后端流程 XML,并按 queued/running/success/failed/skipped/canceled 状态高亮节点。 +- Agent 作为全局工具从顶部打开,只包含发布风险摘要、失败诊断、Runbook 问答、发布说明草稿,并通过后端 Agent API 创建调用记录。 + +## 技术栈 + +- Vue 3 +- Vite +- TypeScript +- Element Plus +- Pinia +- Vue Router +- Axios +- bpmn-js + +## 本地启动 + +```bash +pnpm install +pnpm dev +``` + +The local web app listens on `http://localhost:4301` by default. Use `VITE_API_BASE_URL=http://localhost:4300` when switching from mock services to the local API. + +## 构建检查 + +```bash +pnpm build +``` + +## API 契约来源 + +接口入口为 `src/services/devopsApi.ts`,默认优先调用后端统一 envelope 响应;后端不可用或 `VITE_DEVOPS_USE_MOCK=true` 时降级到 `src/mocks/devops.ts`。后续以 `/Users/mac033/Desktop/my-project/devops-platform-docs/devops-platform-implementation-tracker.md` 中的产品约束和后端 OpenAPI 为准继续收敛字段。 + +浏览器端不得保存或直连以下密钥和服务: + +- Jenkins token +- Gitea token +- 通知 webhook +- LLM API key/baseURL/model + +## BPMN 边界 + +- 前端只负责 BPMN XML 展示和节点状态高亮。 +- `run.steps[].bpmnNodeId` 必须与 BPMN XML 节点 id 一致。 +- 流程推进、失败停留、审计日志和状态机由后端负责。 + +## Agent 边界 + +前端不提供泛聊天入口。Agent 入口仅围绕 DevOps 运维场景: + +- 发布风险摘要 +- 失败诊断 +- Runbook 问答 +- 发布说明草稿 + +模型调用、脱敏、审计、白名单知识库和密钥管理由后端代理实现。 diff --git a/RTK.md b/RTK.md new file mode 100644 index 0000000..170839f --- /dev/null +++ b/RTK.md @@ -0,0 +1,38 @@ +# DevOps 运维平台前端 RTK + +## 项目定位 + +`devops-platform-web` 是 DevOps 运维平台前端,用于统一查看和操作当前四个项目的部署与发布流程: + +- `access-manage` +- `role-admin` +- `role-user` +- `my-resume` + +第一阶段为本地 mock UI,后续接入 `devops-platform-api`。 + +## 页面结构 + +- `/login`:mock 登录。 +- `/overview`:总览,展示服务状态、最近发布和项目环境。 +- `/projects`:项目配置,展示 Gitea 仓库、Jenkins Job、企微机器人和环境策略。 +- `/releases`:发布中心,提供发布参数、二次确认、触发、取消、重试和 BPMN 流程状态。 +- `/runs`:运行记录,展示 run 列表和流程轨迹。 +- `/agent`:Agent 辅助,只保留 DevOps 场景能力。 +- `/settings`:系统配置,展示 API、BPMN、Agent、通知边界。 + +## 数据约定 + +- mock 数据入口:`src/mocks/devops.ts` +- API 封装入口:`src/services/devopsApi.ts` +- Axios 实例:`src/services/http.ts` +- 平台状态:`src/stores/platform.ts` +- 登录状态:`src/stores/session.ts` + +## 后续接入点 + +- 用后端 OpenAPI 替换 `devopsApi` mock 返回。 +- 发布中心触发按钮接入后端发布单创建接口。 +- 运行记录接入后端 run/step 查询接口。 +- BPMN XML 由后端流程定义接口返回。 +- Agent 面板调用后端 Agent proxy,前端只传 DevOps 场景参数。 diff --git a/index.html b/index.html new file mode 100644 index 0000000..1ed866f --- /dev/null +++ b/index.html @@ -0,0 +1,13 @@ + + + + + + + devops-platform-web + + +
+ + + diff --git a/package.json b/package.json new file mode 100644 index 0000000..ef9c538 --- /dev/null +++ b/package.json @@ -0,0 +1,28 @@ +{ + "name": "devops-platform-web", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vue-tsc -b && vite build", + "preview": "vite preview" + }, + "dependencies": { + "@element-plus/icons-vue": "^2.3.2", + "axios": "^1.17.0", + "bpmn-js": "^18.18.0", + "element-plus": "^2.14.1", + "pinia": "^3.0.4", + "vue": "^3.5.34", + "vue-router": "^5.1.0" + }, + "devDependencies": { + "@types/node": "^24.12.3", + "@vitejs/plugin-vue": "^6.0.6", + "@vue/tsconfig": "^0.9.1", + "typescript": "~6.0.2", + "vite": "^8.0.12", + "vue-tsc": "^3.2.8" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..eecc278 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,1762 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@element-plus/icons-vue': + specifier: ^2.3.2 + version: 2.3.2(vue@3.5.38(typescript@6.0.3)) + axios: + specifier: ^1.17.0 + version: 1.17.0 + bpmn-js: + specifier: ^18.18.0 + version: 18.18.0 + element-plus: + specifier: ^2.14.1 + version: 2.14.1(vue@3.5.38(typescript@6.0.3)) + pinia: + specifier: ^3.0.4 + version: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + vue: + specifier: ^3.5.34 + version: 3.5.38(typescript@6.0.3) + vue-router: + specifier: ^5.1.0 + version: 5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@24.13.2)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + devDependencies: + '@types/node': + specifier: ^24.12.3 + version: 24.13.2 + '@vitejs/plugin-vue': + specifier: ^6.0.6 + version: 6.0.7(vite@8.0.16(@types/node@24.13.2)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)) + '@vue/tsconfig': + specifier: ^0.9.1 + version: 0.9.1(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + typescript: + specifier: ~6.0.2 + version: 6.0.3 + vite: + specifier: ^8.0.12 + version: 8.0.16(@types/node@24.13.2)(yaml@2.9.0) + vue-tsc: + specifier: ^3.2.8 + version: 3.3.4(typescript@6.0.3) + +packages: + + '@babel/generator@8.0.0-rc.6': + resolution: {integrity: sha512-6mIzgVK8DgEzvIapoQwhXTMnnkuE4STQmVv9H03i/tZ2ml8oev3TRvZJgTenK2Bsq0YWNtzOrFdTyNzCMFtjJQ==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-string-parser@7.29.7': + resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@8.0.0-rc.6': + resolution: {integrity: sha512-BCkFy+zN6kXQed3YOT7aJl93NfDSzQc3pBfsvTVPs9gU9X3V0aefEF5kwBT0E+mDWH9QgKaZstYUQN9VdQZT4g==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/helper-validator-identifier@7.29.7': + resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@8.0.0-rc.6': + resolution: {integrity: sha512-nVJ+1JcCgntv8d78rRo++o2wuODT0Irknx2BF8Np4Ft2CRgjLqIs4qzSZ8b66yGbBdMWGmZBO9WEZv1hhNiSpg==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@babel/parser@7.29.7': + resolution: {integrity: sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/parser@8.0.0-rc.6': + resolution: {integrity: sha512-rOS8IpdO7mQELkTPlCsTgPejO0bFuZdEDCGQJouYbYf9e1FLTym7Fei2pEjq8q7MWbX0ravcd7QQYKs1TxOuog==} + engines: {node: ^22.18.0 || >=24.11.0} + hasBin: true + + '@babel/types@7.29.7': + resolution: {integrity: sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==} + engines: {node: '>=6.9.0'} + + '@babel/types@8.0.0-rc.6': + resolution: {integrity: sha512-p7/ABylAYlexb31wtRdIfH9L9A0Z2T/9H6zAqzqndkY2PLkvNNc580wGhp/gGKN4Sp9sQvSkhc6Oga8/O+wTyw==} + engines: {node: ^22.18.0 || >=24.11.0} + + '@bpmn-io/diagram-js-ui@0.2.4': + resolution: {integrity: sha512-I678ZGtoH7z7FgzFhzzppM9ThsJ3Lh83kW3GjhjG3xyYciIPYHMaHK1MABP8F69H9pFZpViM6t4qUo8dwrVz0w==} + + '@ctrl/tinycolor@4.2.0': + resolution: {integrity: sha512-kzyuwOAQnXJNLS9PSyrk0CWk35nWJW/zl/6KvnTBMFK65gm7U1/Z5BqjxeapjZCIhQcM/DsrEmcbRwDyXyXK4A==} + engines: {node: '>=14'} + + '@element-plus/icons-vue@2.3.2': + resolution: {integrity: sha512-OzIuTaIfC8QXEPmJvB4Y4kw34rSXdCJzxcD1kFStBvr8bK6X1zQAYDo0CNMjojnfTqRQCJ0I7prlErcoRiET2A==} + peerDependencies: + vue: ^3.2.0 + + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} + + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} + + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} + + '@floating-ui/core@1.7.5': + resolution: {integrity: sha512-1Ih4WTWyw0+lKyFMcBHGbb5U5FtuHJuujoyyr5zTaWS5EYMeT6Jb2AuDeftsCsEuchO+mM2ij5+q9crhydzLhQ==} + + '@floating-ui/dom@1.7.6': + resolution: {integrity: sha512-9gZSAI5XM36880PPMm//9dfiEngYoC6Am2izES1FF406YFsjvyBMmeJ2g4SAju3xWwtuynNRFL2s9hgxpLI5SQ==} + + '@floating-ui/utils@0.2.11': + resolution: {integrity: sha512-RiB/yIh78pcIxl6lLMG0CgBXAZ2Y0eVHqMPYugu+9U0AeT6YBeiJpf7lbdJNIugFP5SIjwNRgo4DhR1Qxi26Gg==} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@napi-rs/wasm-runtime@1.1.5': + resolution: {integrity: sha512-AWPoBRJ9tsnVhor4sjO7rkni+7p+2IAEFj6cx06UgP10jkQHqay/36uRV/bFkgrh18D9vb4cr8Q0Pthskgzy+Q==} + peerDependencies: + '@emnapi/core': ^1.7.1 + '@emnapi/runtime': ^1.7.1 + + '@oxc-project/types@0.133.0': + resolution: {integrity: sha512-KzkdCd6Uxqnf6l3HOw1xfatAlUURA0g14cvBYFyJ5SaNOQbOUvBr9PKArcPcrNIeRsBdgcUzOGrhKveVpvOIGA==} + + '@rolldown/binding-android-arm64@1.0.3': + resolution: {integrity: sha512-454rs7jHngixp/NMxd5srYD57OnzSlZ/eFTETjORQHLwJG1lRtmNOJcBerZlfu4GjKqeq8aCCIQrMdHyhI51Hw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [android] + + '@rolldown/binding-darwin-arm64@1.0.3': + resolution: {integrity: sha512-PcAhP+ynjURNyy8SKGl5DQP94aGuB/7JrXJb/t7P+hanXvQVMWzUvRRhBAcg/lNRadBhoUPqSoP4xw5tR/KBEA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [darwin] + + '@rolldown/binding-darwin-x64@1.0.3': + resolution: {integrity: sha512-9YpfeUvSE2RS7wysJ81uOZkXJz7f7Q55H2Gvp3VEw/EsahqDtrphrZ0EwDLK5vvKOzaCrBsjF8JmnMLcUt78Gg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [darwin] + + '@rolldown/binding-freebsd-x64@1.0.3': + resolution: {integrity: sha512-yB1IlAsSNHncV6SCTL27/MVGR5htvQsoGxIv5KMGXALp+Ll1wYsn+x98M9MW7qa+NdSbvrrY7ANI4wLJ0n1e6g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [freebsd] + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + resolution: {integrity: sha512-Yi30IVAAfLUCy2MseFjbB1jAMDl1VMCAas5StnYp8da9+CKvMd2H2cbEjWcw5NPaPqzvYkVIaF1nNUG+b7u/sw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm] + os: [linux] + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + resolution: {integrity: sha512-jsO7R8To+AdlYgUmN5sHSCZbfhtMBkO0WUx8iORQnPcMMdgr7qM2DQmMwgabs3GhNztdmoKkMKQFHD6DTMCIQw==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-arm64-musl@1.0.3': + resolution: {integrity: sha512-VWkUHwWriDciit80wleYwKILoR/KMvxh/IdwS/paX+ZgpuRpCrKLUdadJbc0NpBEiyhpYawsJ73j9aCvOH+f7Q==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [linux] + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + resolution: {integrity: sha512-5f1laC0SlIR0yDbFCd8acUhvJIag6N3zC5P7oUPN6wX0aOma+uKJ0wBDH5aq7I1PVI2ttTlhJwzwRIBnLiSGEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [ppc64] + os: [linux] + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + resolution: {integrity: sha512-Iq4ko0r4XsgbrF/LunNgHtAGLRRVE2kXonAXQ/MV0mC6jQpMOhW1SvtZja2EhC/kd05++bP78dsqBeIQyYJ6Yg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [s390x] + os: [linux] + + '@rolldown/binding-linux-x64-gnu@1.0.3': + resolution: {integrity: sha512-B8m6tD5+/N5FeNQFbKlLA/2yVq9ycQP1SeedyEYYKWBNR3ZQbkvIUcNnDNM03lO1l5F2roiiFJGgvoLLyZXtSg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-linux-x64-musl@1.0.3': + resolution: {integrity: sha512-pSdpdUJHkuCxun9LE7jvgUB9qsRgaiyNNCX7m/AvHTcq67AiT/Yhoxvw5zPfhrM8k/BfP8ce/hMOpthKDpEUow==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [linux] + + '@rolldown/binding-openharmony-arm64@1.0.3': + resolution: {integrity: sha512-OXXS3RKJgX2uLwM+gYyuH5omcH8fL1LJs96pZGgtetVCahON57+d4SJHzTgZiOjxgGkSnpXpOsWuPDGAKAigEg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [openharmony] + + '@rolldown/binding-wasm32-wasi@1.0.3': + resolution: {integrity: sha512-JTtb8BWFynicNSoPrehsCzBtOKjZ6jhMiPFEmOiuXg1Fl8dn2KHQob+GuPSGR0dryQa1PQJbzjF3dqO/whhjLg==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [wasm32] + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + resolution: {integrity: sha512-gEdFFEN70A/jxb2svrWsN3aDL7OUtmvlOy+6fa2jxG8K0wQ1ZbdeLGnidov6Yu5/733dI5ySfzFlQ/cb0bSz1g==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [arm64] + os: [win32] + + '@rolldown/binding-win32-x64-msvc@1.0.3': + resolution: {integrity: sha512-eXB7CHuaQdqmJcc3koCNtNPmT/bj2gc999kUFgBxG8Ac0NdgXc4rkCHhqrgrhN3zddvvvrgzj1e90SuSfmyIXA==} + engines: {node: ^20.19.0 || >=22.12.0} + cpu: [x64] + os: [win32] + + '@rolldown/pluginutils@1.0.1': + resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} + + '@sxzz/popperjs-es@2.11.8': + resolution: {integrity: sha512-wOwESXvvED3S8xBmcPWHs2dUuzrE4XiZeFu7e1hROIJkm02a49N120pmOXxY33sBb6hArItm5W5tcg1cBtV+HQ==} + + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} + + '@types/jsesc@2.5.1': + resolution: {integrity: sha512-9VN+6yxLOPLOav+7PwjZbxiID2bVaeq0ED4qSQmdQTdjnXJSaCVKTR58t15oqH1H5t8Ng2ZX1SabJVoN9Q34bw==} + + '@types/lodash-es@4.17.12': + resolution: {integrity: sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==} + + '@types/lodash@4.17.24': + resolution: {integrity: sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==} + + '@types/node@24.13.2': + resolution: {integrity: sha512-fRa09kZTgu8o71KFcDjUFuc7F+dEbZYZmkI0mg5YBTRs0yMKjYHsq/c0urDKeDb+D5qVgXOdFcuu+DZPKOITwA==} + + '@types/web-bluetooth@0.0.21': + resolution: {integrity: sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==} + + '@vitejs/plugin-vue@6.0.7': + resolution: {integrity: sha512-km+p+XdSz9Sxm5rqUbqcSfZYaAniKxWBj1KURl+Jr7UaPvvX7BmaWMdP69I5rrFDeQGyxAG7NXdc57vz+snhWg==} + engines: {node: ^20.19.0 || >=22.12.0} + peerDependencies: + vite: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0 + vue: ^3.2.25 + + '@volar/language-core@2.4.28': + resolution: {integrity: sha512-w4qhIJ8ZSitgLAkVay6AbcnC7gP3glYM3fYwKV3srj8m494E3xtrCv6E+bWviiK/8hs6e6t1ij1s2Endql7vzQ==} + + '@volar/source-map@2.4.28': + resolution: {integrity: sha512-yX2BDBqJkRXfKw8my8VarTyjv48QwxdJtvRgUpNE5erCsgEUdI2DsLbpa+rOQVAJYshY99szEcRDmyHbF10ggQ==} + + '@volar/typescript@2.4.28': + resolution: {integrity: sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw==} + + '@vue-macros/common@3.1.2': + resolution: {integrity: sha512-h9t4ArDdniO9ekYHAD95t9AZcAbb19lEGK+26iAjUODOIJKmObDNBSe4+6ELQAA3vtYiFPPBtHh7+cQCKi3Dng==} + engines: {node: '>=20.19.0'} + peerDependencies: + vue: ^2.7.0 || ^3.2.25 + peerDependenciesMeta: + vue: + optional: true + + '@vue/compiler-core@3.5.38': + resolution: {integrity: sha512-s99aGxWYig9ErHbct27KXEGhrBYlRI6c4MwAgXErOAbX9xiW37/uMa+XUDO69zLz83dng8UUZ70CTOJrLrYrEQ==} + + '@vue/compiler-dom@3.5.38': + resolution: {integrity: sha512-JTqp25l8aFfJYF7/KmsXZjAxJz7T+SjmTJLoXVjHtc2BrSgSiW2n9Aem/cWq1OPe68A8JL06B3eVdhlP0H4TVw==} + + '@vue/compiler-sfc@3.5.38': + resolution: {integrity: sha512-DuA2GiZawSEW442iw/9+Fkol8hTgb4Ke5KkhmSry65QA7YuyMbIdy8p0XZRMvNwJdgRz307W8g1CSzdvS4nuNg==} + + '@vue/compiler-ssr@3.5.38': + resolution: {integrity: sha512-7s+W5Gc42FGxZMcuwl8H5B29T8BJPMdBT7KHFE+BbAuZ/iTEdTtv7z2XiMjiaUUw4w3ZcCEdHs36RuYJ2VA7bA==} + + '@vue/devtools-api@7.7.9': + resolution: {integrity: sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==} + + '@vue/devtools-api@8.1.2': + resolution: {integrity: sha512-vA0O112YqyDuNA1s7Yb2gCgToQ/OxOWiFDO5ThLCcDy0ldHnSd1dUTaSYhOldbqoNgumE4dxtGAoAaSUKUD1Zg==} + + '@vue/devtools-kit@7.7.9': + resolution: {integrity: sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==} + + '@vue/devtools-kit@8.1.2': + resolution: {integrity: sha512-f75/upc+GCyjXErpgPGz4582ujS0L/adAltGy+tqXMGUJpgAcfGr6CxnnhpZY8BHuMYt6KpbF8uaFrrQG66rGQ==} + + '@vue/devtools-shared@7.7.9': + resolution: {integrity: sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==} + + '@vue/devtools-shared@8.1.2': + resolution: {integrity: sha512-X9RyVFYAdkBe4IUf5v48TxBF/6QPmF8CmWrDAjXzfUHrgQ/HGfTC1A6TqgXqZ03ye66l3AD51BAGD69IvKM9sw==} + + '@vue/language-core@3.3.4': + resolution: {integrity: sha512-IuHqQ5zGGOE7CXP72VX6A42IVeIzYv4WAhO6arej11TRNqtdZfGyH8Yr2FOCaDX0dSQG+JwULLoFHGY1igYVjQ==} + + '@vue/reactivity@3.5.38': + resolution: {integrity: sha512-pG6LV/NDNRbKizcUjFFLAfjaL8mcv4DmR9avNcUw2gDHBzZneuS2TWCmp633ynzxz9YYKNeEPK2I8Wraqy2HUQ==} + + '@vue/runtime-core@3.5.38': + resolution: {integrity: sha512-iyW8WVfF1CpCXxncZY5Ei6rSd6oZr5DgEom//fUjRBRl56AXPD+s9ATvukRt77ZFTuYlnVA1bxY+dJB94tWVYw==} + + '@vue/runtime-dom@3.5.38': + resolution: {integrity: sha512-apX2wt9sdfDshS+a2xueFZLVpt0GkRJZSoPmrW/SA4yzXTznhfcMVW59gr7h4YQeY0vJhdJkk2rsIDwgfFgC5A==} + + '@vue/server-renderer@3.5.38': + resolution: {integrity: sha512-vue8vbf2QlV4quHqzwmJy6dWfmRhP1J8l4wtZg60CL6VoKqcPY2oe7may3+1d9qfpedjK5PRLFqd5k3Isj9mUw==} + peerDependencies: + vue: 3.5.38 + + '@vue/shared@3.5.38': + resolution: {integrity: sha512-FTW0AFZNaK5/mOqvGBwVfUlNLU38TiQn4+DQgIFUnrBBJQ1crMJ82yeGQLV5jyKFsO8yRukpbuP7x+nRbH6aug==} + + '@vue/tsconfig@0.9.1': + resolution: {integrity: sha512-buvjm+9NzLCJL29KY1j1991YYJ5e6275OiK+G4jtmfIb+z4POywbdm0wXusT9adVWqe0xqg70TbI7+mRx4uU9w==} + peerDependencies: + typescript: '>= 5.8' + vue: ^3.4.0 + peerDependenciesMeta: + typescript: + optional: true + vue: + optional: true + + '@vueuse/core@14.3.0': + resolution: {integrity: sha512-aHfz47g0ZhMtTVHmIzMVpJy8ePhhOy68GY5bv110+5DVtZ+W7BsOx+m61UNQqfrWyPztIHIanWa3E2tib3NFIw==} + peerDependencies: + vue: ^3.5.0 + + '@vueuse/metadata@14.3.0': + resolution: {integrity: sha512-BwxmbAzwAVF50+MW57GXOUEV61nFBGnlBvrTqj49PqWJu3uw7hdu72ztXeZ33RdZtDY6kO+bfCAE1PCn88Tktw==} + + '@vueuse/shared@14.3.0': + resolution: {integrity: sha512-bZpge9eSXwa4ToSiqJ7j6KRwhAsneMFoSz3LMWKQDkqimm3D/tbFlrklrs/IOqC8tEcYmXQZJ6N0UrjhBirVCg==} + peerDependencies: + vue: ^3.5.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + + alien-signals@3.2.1: + resolution: {integrity: sha512-I8FjmltrfnDFoZedi5CG8DghVYNhzb/Ijluz7tCSJH0xpd0484Kowhbb1XDYOxfJpU1p5wnM2X54dA+IfGyD1g==} + + ast-kit@2.2.0: + resolution: {integrity: sha512-m1Q/RaVOnTp9JxPX+F+Zn7IcLYMzM8kZofDImfsKZd8MbR+ikdOzTeztStWqfrqIxZnYWryyI9ePm3NGjnZgGw==} + engines: {node: '>=20.19.0'} + + ast-walker-scope@0.9.0: + resolution: {integrity: sha512-IJdzo2vLiElBxKzwS36VsCue/62d6IdWjnPB2v3nuPKeWGynp6FF/CYoLa5i/3jXH/z97ZDdsXz6abpgM6w07A==} + engines: {node: '>=20.19.0'} + + async-validator@4.2.5: + resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==} + + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + + axios@1.17.0: + resolution: {integrity: sha512-J8SwNxprqqpbfenehxWYXE7CW+wM1BB4w3+N+g+/Wx40xM4rsLrfPmHHxSWIxJLYDgSY/HqlFPIYb2/S3rxafw==} + + birpc@2.9.0: + resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==} + + bpmn-js@18.18.0: + resolution: {integrity: sha512-3pTiqc5M+CRtXCvBjRBFEewqNKDnx0ytuCLucvMRfdhes/EuVr4memcsi6ql+q/1RUbycOL9MyfNcgrwSfnA1w==} + + bpmn-moddle@10.0.0: + resolution: {integrity: sha512-vXePD5jkatcILmM3zwJG/m6IIHIghTGB7WvgcdEraEw8E8VdJHrTgrvBUhbzqaXJpnsGQz15QS936xeBY6l9aA==} + engines: {node: '>= 20.12'} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + chokidar@5.0.0: + resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} + engines: {node: '>= 20.19.0'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + copy-anything@4.0.5: + resolution: {integrity: sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==} + engines: {node: '>=18'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + dayjs@1.11.21: + resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + diagram-js-direct-editing@3.4.0: + resolution: {integrity: sha512-eXfd+nxJr5xt3YlCrs+E53mPi+gqBWRBkAXkR99ZPL7Pl0B3H5nkv/uiwx4Q4vJU/Sp8Jy3zVdhP/Y2c6loTjw==} + peerDependencies: + diagram-js: '*' + + diagram-js@15.17.0: + resolution: {integrity: sha512-wOlnfzd7MmcUMwIpJiCiFScQ04hQMx4Ribir0ky4cfNgzwr7V1dhi7MdFuLyaMdseglMkBU7YYoqmzCDR8+JCw==} + + didi@11.0.0: + resolution: {integrity: sha512-PzCfRzQttvFpVcYMbSF7h8EsWjeJpVjWH4qDhB5LkMi1ILvHq4Ob0vhM2wLFziPkbUBi+PAo7ODbe2sacR7nJQ==} + engines: {node: '>= 20.12'} + + domify@3.0.0: + resolution: {integrity: sha512-bs2yO68JDFOm6rKv8f0EnrM2cENduhRkpqOtt/s5l5JBA/eqGBZCzLPmdYoHtJ6utgLGgcBajFsEQbl12pT0lQ==} + engines: {node: '>=20'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + element-plus@2.14.1: + resolution: {integrity: sha512-UFnm1+BckNi+azkKJ7L32q1uXs9ekr99Z9pWTQPeDR05jqEWUwQq51ro4kZMVrANbjknX3Z7ukCZwTi2T6Tr9A==} + peerDependencies: + vue: ^3.3.7 + + entities@7.0.1: + resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==} + engines: {node: '>=0.12'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-object-atoms@1.1.2: + resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + follow-redirects@1.16.0: + resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + + form-data@4.0.5: + resolution: {integrity: sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==} + engines: {node: '>= 6'} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.4: + resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==} + engines: {node: '>= 0.4'} + + hookable@5.5.3: + resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==} + + htm@3.1.1: + resolution: {integrity: sha512-983Vyg8NwUE7JkZ6NmOqpCZ+sh1bKv2iYTlUkzlWmA5JD2acKoxd4KVxbMmxX/85mtfdnDmTFoNKcg5DGAvxNQ==} + + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + + ids@3.0.2: + resolution: {integrity: sha512-t6YJP4mdC+GHF96Nbis/4FEANhP/8VWmYMvUuYpXvSdrhg5hpIVbq2XZlOA3UWTbtdwPCi0q7jEXOdHkAnqOnw==} + engines: {node: '>= 20.12'} + + inherits-browser@0.1.0: + resolution: {integrity: sha512-CJHHvW3jQ6q7lzsXPpapLdMx5hDpSF3FSh45pwsj6bKxJJ8Nl8v43i5yXnr3BdfOimGHKyniewQtnAIp3vyJJw==} + + is-what@5.5.0: + resolution: {integrity: sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==} + engines: {node: '>=18'} + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + local-pkg@1.2.1: + resolution: {integrity: sha512-++gUqRDEvcnN6Zhqrr+y/CkVEHhlrR96vZn3nZZPYzMcBUyBtTKzB9NadClFIsIVSsu+3i9tfk/erqy9kAmt7Q==} + engines: {node: '>=14'} + + lodash-es@4.18.1: + resolution: {integrity: sha512-J8xewKD/Gk22OZbhpOVSwcs60zhd95ESDwezOFuA3/099925PdHJ7OFHNTGtajL3AlZkykD32HykiMo+BIBI8A==} + + lodash-unified@1.0.3: + resolution: {integrity: sha512-WK9qSozxXOD7ZJQlpSqOT+om2ZfcT4yO+03FuzAHD0wF6S0l0090LRPDx3vhTTLZ8cFKpBn+IOcVXK6qOcIlfQ==} + peerDependencies: + '@types/lodash-es': '*' + lodash: '*' + lodash-es: '*' + + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + + magic-string-ast@1.0.3: + resolution: {integrity: sha512-CvkkH1i81zl7mmb94DsRiFeG9V2fR2JeuK8yDgS8oiZSFa++wWLEgZ5ufEOyLHbvSbD1gTRKv9NdX69Rnvr9JA==} + engines: {node: '>=20.19.0'} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + memoize-one@6.0.0: + resolution: {integrity: sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + min-dash@5.0.0: + resolution: {integrity: sha512-EGuoBnVL7/Fnv2sqakpX5WGmZehZ3YMmLayT7sM8E9DRU74kkeyMg4Rik1lsOkR2GbFNeBca4/L+UfU6gF0Edw==} + + min-dom@5.3.0: + resolution: {integrity: sha512-0w5FEBgPAyHhmFojW3zxd7we3D+m5XYS3E/06OyvxmbHJoiQVa4Nagj6RWvoAKYRw5xth6cP5TMePc5cR1M9hA==} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + mlly@1.8.2: + resolution: {integrity: sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==} + + moddle-xml@12.0.0: + resolution: {integrity: sha512-NJc2+sCe4tvuGlaUBcoZcYf6j9f+z+qxHOyGm/LB3ZrlJXVPPHoBTg/KXgDRCufdBJhJ3AheFs3QU/abABNzRg==} + engines: {node: '>= 18'} + peerDependencies: + moddle: '>= 6.2.0' + + moddle@8.1.0: + resolution: {integrity: sha512-dBddc1CNuZHgro8nQWwfPZ2BkyLWdnxoNpPu9d+XKPN96DAiiBOeBw527ft++ebDuFez5PMdaR3pgUgoOaUGrA==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.12: + resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + normalize-wheel-es@1.2.0: + resolution: {integrity: sha512-Wj7+EJQ8mSuXr2iWfnujrimU35R2W4FAErEyTmJoJ7ucwTn2hOUSsRehMb5RSYkxXGTM7Y9QpvPmp++w5ftoJw==} + + object-refs@0.4.0: + resolution: {integrity: sha512-6kJqKWryKZmtte6QYvouas0/EIJKPI1/MMIuRsiBlNuhIMfqYTggzX2F1AJ2+cDs288xyi9GL7FyasHINR98BQ==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-intersection@4.1.0: + resolution: {integrity: sha512-urUP6WvhnxbHPdHYl6L7Yrc6+1ny6uOFKPCzPxTSUSYGHG0o94RmI7SvMMaScNAM5RtTf08bg4skc6/kjfne3A==} + engines: {node: '>= 14.20'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + perfect-debounce@1.0.0: + resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==} + + perfect-debounce@2.1.0: + resolution: {integrity: sha512-LjgdTytVFXeUgtHZr9WYViYSM/g8MkcTPYDlPa3cDqMirHjKiSZPYd6DoL7pK8AJQr+uWkQvCjHNdiMqsrJs+g==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + + pinia@3.0.4: + resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==} + peerDependencies: + typescript: '>=4.5.0' + vue: ^3.5.11 + peerDependenciesMeta: + typescript: + optional: true + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.1: + resolution: {integrity: sha512-y+ichcgc2LrADuhLNAx8DFjVfgz91pRxfZdI3UDhxHvcVEZsenLO+7XaU5vOp0u/7V/wZ+plyuQxtrDlZJ+yeg==} + + postcss@8.5.15: + resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==} + engines: {node: ^10 || ^12 || >=14} + + preact@10.29.2: + resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + + proxy-from-env@2.1.0: + resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==} + engines: {node: '>=10'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + readdirp@5.0.0: + resolution: {integrity: sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==} + engines: {node: '>= 20.19.0'} + + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + + rolldown@1.0.3: + resolution: {integrity: sha512-i00lAJ2ks1BYr7rjNjKC7BcqAS7nVfiT3QX1SI5aY+AFHblCmaUf9OE9dbdzDvW6dJxbi2ZCZiy9v3CcwOiX3g==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + + saxen@11.0.2: + resolution: {integrity: sha512-WDb4gqac8uiJzOdOdVpr9NWh9NrJMm7Brn5GX2Poj+mjE/QTXqYQENr8T/mom54dDDgbd3QjwTg23TRHYiWXRA==} + engines: {node: '>= 20.12'} + + scule@1.3.0: + resolution: {integrity: sha512-6FtHJEvt+pVMIB9IBY+IcCJ6Z5f1iQnytgyfKMhDKgmzYG+TeH/wx1y3l27rshSbLiSanrR9ffZDrEsmjlQF2g==} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + speakingurl@14.0.1: + resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==} + engines: {node: '>=0.10.0'} + + superjson@2.2.6: + resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==} + engines: {node: '>=16'} + + tiny-svg@4.1.4: + resolution: {integrity: sha512-cBaEACCbouYrQc9RG+eTXnPYosX1Ijqty/I6DdXovwDd89Pwu4jcmpOR7BuFEF9YCcd7/AWwasE0207WMK7hdw==} + engines: {node: '>= 20'} + + tinyglobby@0.2.17: + resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} + engines: {node: '>=12.0.0'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + unplugin-utils@0.3.1: + resolution: {integrity: sha512-5lWVjgi6vuHhJ526bI4nlCOmkCIF3nnfXkCMDeMJrtdvxTs6ZFCM8oNufGTsDbKv/tJ/xj8RpvXjRuPBZJuJog==} + engines: {node: '>=20.19.0'} + + unplugin@3.0.0: + resolution: {integrity: sha512-0Mqk3AT2TZCXWKdcoaufeXNukv2mTrEZExeXlHIOZXdqYoHHr4n51pymnwV8x2BOVxwXbK2HLlI7usrqMpycdg==} + engines: {node: ^20.19.0 || >=22.12.0} + + vite@8.0.16: + resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + '@vitejs/devtools': ^0.1.18 + esbuild: ^0.27.0 || ^0.28.0 + jiti: '>=1.21.0' + less: ^4.0.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + '@vitejs/devtools': + optional: true + esbuild: + optional: true + jiti: + optional: true + less: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + vue-component-type-helpers@3.3.4: + resolution: {integrity: sha512-joip1uZTaQR0nD23N400gIdJ7xY+WiiiMA/BCKz842gvGBknqDQAzklUvDEhqFvvrhQY8S2ZANBMu4X70VMFGw==} + + vue-router@5.1.0: + resolution: {integrity: sha512-HAbiLzLEHQwxPgvsbOJDAwtavszEgLwri6XfyrsPECIFez8+59xc9LofWVdc/HEaSRT822lJ8H9Ns38VVond5g==} + peerDependencies: + '@pinia/colada': '>=0.21.2' + '@vue/compiler-sfc': ^3.5.34 + pinia: ^3.0.4 + vite: ^7.0.0 || ^8.0.0 + vue: ^3.5.34 + peerDependenciesMeta: + '@pinia/colada': + optional: true + '@vue/compiler-sfc': + optional: true + pinia: + optional: true + vite: + optional: true + + vue-tsc@3.3.4: + resolution: {integrity: sha512-XA/JqmQwS2GZmfgpjOEGdrKwaTSEuPwxpHa7/t6f4yiGrJb3gVHTPb9wBfByMNZwQ+xDXs41b8gaS2DKsOozUw==} + hasBin: true + peerDependencies: + typescript: '>=5.0.0' + + vue@3.5.38: + resolution: {integrity: sha512-vAMKHfImQlYSy0C+PBue4s3ERZ2xGKfgZg5GXAsLInq1dyh2H78ILVP5sK0KPFPVW4kv+OGCIvBEondcjpZp7A==} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + webpack-virtual-modules@0.6.2: + resolution: {integrity: sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==} + + yaml@2.9.0: + resolution: {integrity: sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==} + engines: {node: '>= 14.6'} + hasBin: true + +snapshots: + + '@babel/generator@8.0.0-rc.6': + dependencies: + '@babel/parser': 8.0.0-rc.6 + '@babel/types': 8.0.0-rc.6 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + '@types/jsesc': 2.5.1 + jsesc: 3.1.0 + + '@babel/helper-string-parser@7.29.7': {} + + '@babel/helper-string-parser@8.0.0-rc.6': {} + + '@babel/helper-validator-identifier@7.29.7': {} + + '@babel/helper-validator-identifier@8.0.0-rc.6': {} + + '@babel/parser@7.29.7': + dependencies: + '@babel/types': 7.29.7 + + '@babel/parser@8.0.0-rc.6': + dependencies: + '@babel/types': 8.0.0-rc.6 + + '@babel/types@7.29.7': + dependencies: + '@babel/helper-string-parser': 7.29.7 + '@babel/helper-validator-identifier': 7.29.7 + + '@babel/types@8.0.0-rc.6': + dependencies: + '@babel/helper-string-parser': 8.0.0-rc.6 + '@babel/helper-validator-identifier': 8.0.0-rc.6 + + '@bpmn-io/diagram-js-ui@0.2.4': + dependencies: + htm: 3.1.1 + preact: 10.29.2 + + '@ctrl/tinycolor@4.2.0': {} + + '@element-plus/icons-vue@2.3.2(vue@3.5.38(typescript@6.0.3))': + dependencies: + vue: 3.5.38(typescript@6.0.3) + + '@emnapi/core@1.10.0': + dependencies: + '@emnapi/wasi-threads': 1.2.1 + tslib: 2.8.1 + optional: true + + '@emnapi/runtime@1.10.0': + dependencies: + tslib: 2.8.1 + optional: true + + '@emnapi/wasi-threads@1.2.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@floating-ui/core@1.7.5': + dependencies: + '@floating-ui/utils': 0.2.11 + + '@floating-ui/dom@1.7.6': + dependencies: + '@floating-ui/core': 1.7.5 + '@floating-ui/utils': 0.2.11 + + '@floating-ui/utils@0.2.11': {} + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@napi-rs/wasm-runtime@1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0)': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 + optional: true + + '@oxc-project/types@0.133.0': {} + + '@rolldown/binding-android-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-arm64@1.0.3': + optional: true + + '@rolldown/binding-darwin-x64@1.0.3': + optional: true + + '@rolldown/binding-freebsd-x64@1.0.3': + optional: true + + '@rolldown/binding-linux-arm-gnueabihf@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-arm64-musl@1.0.3': + optional: true + + '@rolldown/binding-linux-ppc64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-s390x-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-gnu@1.0.3': + optional: true + + '@rolldown/binding-linux-x64-musl@1.0.3': + optional: true + + '@rolldown/binding-openharmony-arm64@1.0.3': + optional: true + + '@rolldown/binding-wasm32-wasi@1.0.3': + dependencies: + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@napi-rs/wasm-runtime': 1.1.5(@emnapi/core@1.10.0)(@emnapi/runtime@1.10.0) + optional: true + + '@rolldown/binding-win32-arm64-msvc@1.0.3': + optional: true + + '@rolldown/binding-win32-x64-msvc@1.0.3': + optional: true + + '@rolldown/pluginutils@1.0.1': {} + + '@sxzz/popperjs-es@2.11.8': {} + + '@tybys/wasm-util@0.10.2': + dependencies: + tslib: 2.8.1 + optional: true + + '@types/jsesc@2.5.1': {} + + '@types/lodash-es@4.17.12': + dependencies: + '@types/lodash': 4.17.24 + + '@types/lodash@4.17.24': {} + + '@types/node@24.13.2': + dependencies: + undici-types: 7.18.2 + + '@types/web-bluetooth@0.0.21': {} + + '@vitejs/plugin-vue@6.0.7(vite@8.0.16(@types/node@24.13.2)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@rolldown/pluginutils': 1.0.1 + vite: 8.0.16(@types/node@24.13.2)(yaml@2.9.0) + vue: 3.5.38(typescript@6.0.3) + + '@volar/language-core@2.4.28': + dependencies: + '@volar/source-map': 2.4.28 + + '@volar/source-map@2.4.28': {} + + '@volar/typescript@2.4.28': + dependencies: + '@volar/language-core': 2.4.28 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vue-macros/common@3.1.2(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@vue/compiler-sfc': 3.5.38 + ast-kit: 2.2.0 + local-pkg: 1.2.1 + magic-string-ast: 1.0.3 + unplugin-utils: 0.3.1 + optionalDependencies: + vue: 3.5.38(typescript@6.0.3) + + '@vue/compiler-core@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/shared': 3.5.38 + entities: 7.0.1 + estree-walker: 2.0.2 + source-map-js: 1.2.1 + + '@vue/compiler-dom@3.5.38': + dependencies: + '@vue/compiler-core': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/compiler-sfc@3.5.38': + dependencies: + '@babel/parser': 7.29.7 + '@vue/compiler-core': 3.5.38 + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + estree-walker: 2.0.2 + magic-string: 0.30.21 + postcss: 8.5.15 + source-map-js: 1.2.1 + + '@vue/compiler-ssr@3.5.38': + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/devtools-api@7.7.9': + dependencies: + '@vue/devtools-kit': 7.7.9 + + '@vue/devtools-api@8.1.2': + dependencies: + '@vue/devtools-kit': 8.1.2 + + '@vue/devtools-kit@7.7.9': + dependencies: + '@vue/devtools-shared': 7.7.9 + birpc: 2.9.0 + hookable: 5.5.3 + mitt: 3.0.1 + perfect-debounce: 1.0.0 + speakingurl: 14.0.1 + superjson: 2.2.6 + + '@vue/devtools-kit@8.1.2': + dependencies: + '@vue/devtools-shared': 8.1.2 + birpc: 2.9.0 + hookable: 5.5.3 + perfect-debounce: 2.1.0 + + '@vue/devtools-shared@7.7.9': + dependencies: + rfdc: 1.4.1 + + '@vue/devtools-shared@8.1.2': {} + + '@vue/language-core@3.3.4': + dependencies: + '@volar/language-core': 2.4.28 + '@vue/compiler-dom': 3.5.38 + '@vue/shared': 3.5.38 + alien-signals: 3.2.1 + muggle-string: 0.4.1 + path-browserify: 1.0.1 + picomatch: 4.0.4 + + '@vue/reactivity@3.5.38': + dependencies: + '@vue/shared': 3.5.38 + + '@vue/runtime-core@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/shared': 3.5.38 + + '@vue/runtime-dom@3.5.38': + dependencies: + '@vue/reactivity': 3.5.38 + '@vue/runtime-core': 3.5.38 + '@vue/shared': 3.5.38 + csstype: 3.2.3 + + '@vue/server-renderer@3.5.38(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@vue/compiler-ssr': 3.5.38 + '@vue/shared': 3.5.38 + vue: 3.5.38(typescript@6.0.3) + + '@vue/shared@3.5.38': {} + + '@vue/tsconfig@0.9.1(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3))': + optionalDependencies: + typescript: 6.0.3 + vue: 3.5.38(typescript@6.0.3) + + '@vueuse/core@14.3.0(vue@3.5.38(typescript@6.0.3))': + dependencies: + '@types/web-bluetooth': 0.0.21 + '@vueuse/metadata': 14.3.0 + '@vueuse/shared': 14.3.0(vue@3.5.38(typescript@6.0.3)) + vue: 3.5.38(typescript@6.0.3) + + '@vueuse/metadata@14.3.0': {} + + '@vueuse/shared@14.3.0(vue@3.5.38(typescript@6.0.3))': + dependencies: + vue: 3.5.38(typescript@6.0.3) + + acorn@8.16.0: {} + + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + alien-signals@3.2.1: {} + + ast-kit@2.2.0: + dependencies: + '@babel/parser': 7.29.7 + pathe: 2.0.3 + + ast-walker-scope@0.9.0: + dependencies: + '@babel/parser': 7.29.7 + '@babel/types': 7.29.7 + ast-kit: 2.2.0 + + async-validator@4.2.5: {} + + asynckit@0.4.0: {} + + axios@1.17.0: + dependencies: + follow-redirects: 1.16.0 + form-data: 4.0.5 + https-proxy-agent: 5.0.1 + proxy-from-env: 2.1.0 + transitivePeerDependencies: + - debug + - supports-color + + birpc@2.9.0: {} + + bpmn-js@18.18.0: + dependencies: + bpmn-moddle: 10.0.0 + diagram-js: 15.17.0 + diagram-js-direct-editing: 3.4.0(diagram-js@15.17.0) + ids: 3.0.2 + inherits-browser: 0.1.0 + min-dash: 5.0.0 + min-dom: 5.3.0 + tiny-svg: 4.1.4 + + bpmn-moddle@10.0.0: + dependencies: + min-dash: 5.0.0 + moddle: 8.1.0 + moddle-xml: 12.0.0(moddle@8.1.0) + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + chokidar@5.0.0: + dependencies: + readdirp: 5.0.0 + + clsx@2.1.1: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + copy-anything@4.0.5: + dependencies: + is-what: 5.5.0 + + csstype@3.2.3: {} + + dayjs@1.11.21: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + delayed-stream@1.0.0: {} + + detect-libc@2.1.2: {} + + diagram-js-direct-editing@3.4.0(diagram-js@15.17.0): + dependencies: + diagram-js: 15.17.0 + min-dash: 5.0.0 + min-dom: 5.3.0 + + diagram-js@15.17.0: + dependencies: + '@bpmn-io/diagram-js-ui': 0.2.4 + clsx: 2.1.1 + didi: 11.0.0 + inherits-browser: 0.1.0 + min-dash: 5.0.0 + min-dom: 5.3.0 + object-refs: 0.4.0 + path-intersection: 4.1.0 + tiny-svg: 4.1.4 + + didi@11.0.0: {} + + domify@3.0.0: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + element-plus@2.14.1(vue@3.5.38(typescript@6.0.3)): + dependencies: + '@ctrl/tinycolor': 4.2.0 + '@element-plus/icons-vue': 2.3.2(vue@3.5.38(typescript@6.0.3)) + '@floating-ui/dom': 1.7.6 + '@popperjs/core': '@sxzz/popperjs-es@2.11.8' + '@types/lodash': 4.17.24 + '@types/lodash-es': 4.17.12 + '@vueuse/core': 14.3.0(vue@3.5.38(typescript@6.0.3)) + async-validator: 4.2.5 + dayjs: 1.11.21 + lodash: 4.18.1 + lodash-es: 4.18.1 + lodash-unified: 1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1) + memoize-one: 6.0.0 + normalize-wheel-es: 1.2.0 + vue: 3.5.38(typescript@6.0.3) + vue-component-type-helpers: 3.3.4 + + entities@7.0.1: {} + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-object-atoms@1.1.2: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.4 + + estree-walker@2.0.2: {} + + exsolve@1.0.8: {} + + fdir@6.5.0(picomatch@4.0.4): + optionalDependencies: + picomatch: 4.0.4 + + follow-redirects@1.16.0: {} + + form-data@4.0.5: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + es-set-tostringtag: 2.1.0 + hasown: 2.0.4 + mime-types: 2.1.35 + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.2 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.4 + math-intrinsics: 1.1.0 + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.2 + + gopd@1.2.0: {} + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.4: + dependencies: + function-bind: 1.1.2 + + hookable@5.5.3: {} + + htm@3.1.1: {} + + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + ids@3.0.2: {} + + inherits-browser@0.1.0: {} + + is-what@5.5.0: {} + + jsesc@3.1.0: {} + + json5@2.2.3: {} + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + local-pkg@1.2.1: + dependencies: + mlly: 1.8.2 + pkg-types: 2.3.1 + quansync: 0.2.11 + + lodash-es@4.18.1: {} + + lodash-unified@1.0.3(@types/lodash-es@4.17.12)(lodash-es@4.18.1)(lodash@4.18.1): + dependencies: + '@types/lodash-es': 4.17.12 + lodash: 4.18.1 + lodash-es: 4.18.1 + + lodash@4.18.1: {} + + magic-string-ast@1.0.3: + dependencies: + magic-string: 0.30.21 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + math-intrinsics@1.1.0: {} + + memoize-one@6.0.0: {} + + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + min-dash@5.0.0: {} + + min-dom@5.3.0: + dependencies: + domify: 3.0.0 + min-dash: 5.0.0 + + mitt@3.0.1: {} + + mlly@1.8.2: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.4 + + moddle-xml@12.0.0(moddle@8.1.0): + dependencies: + min-dash: 5.0.0 + moddle: 8.1.0 + saxen: 11.0.2 + + moddle@8.1.0: + dependencies: + min-dash: 5.0.0 + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.12: {} + + normalize-wheel-es@1.2.0: {} + + object-refs@0.4.0: {} + + path-browserify@1.0.1: {} + + path-intersection@4.1.0: {} + + pathe@2.0.3: {} + + perfect-debounce@1.0.0: {} + + perfect-debounce@2.1.0: {} + + picocolors@1.1.1: {} + + picomatch@4.0.4: {} + + pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)): + dependencies: + '@vue/devtools-api': 7.7.9 + vue: 3.5.38(typescript@6.0.3) + optionalDependencies: + typescript: 6.0.3 + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.2 + pathe: 2.0.3 + + pkg-types@2.3.1: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + postcss@8.5.15: + dependencies: + nanoid: 3.3.12 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + preact@10.29.2: {} + + proxy-from-env@2.1.0: {} + + quansync@0.2.11: {} + + readdirp@5.0.0: {} + + rfdc@1.4.1: {} + + rolldown@1.0.3: + dependencies: + '@oxc-project/types': 0.133.0 + '@rolldown/pluginutils': 1.0.1 + optionalDependencies: + '@rolldown/binding-android-arm64': 1.0.3 + '@rolldown/binding-darwin-arm64': 1.0.3 + '@rolldown/binding-darwin-x64': 1.0.3 + '@rolldown/binding-freebsd-x64': 1.0.3 + '@rolldown/binding-linux-arm-gnueabihf': 1.0.3 + '@rolldown/binding-linux-arm64-gnu': 1.0.3 + '@rolldown/binding-linux-arm64-musl': 1.0.3 + '@rolldown/binding-linux-ppc64-gnu': 1.0.3 + '@rolldown/binding-linux-s390x-gnu': 1.0.3 + '@rolldown/binding-linux-x64-gnu': 1.0.3 + '@rolldown/binding-linux-x64-musl': 1.0.3 + '@rolldown/binding-openharmony-arm64': 1.0.3 + '@rolldown/binding-wasm32-wasi': 1.0.3 + '@rolldown/binding-win32-arm64-msvc': 1.0.3 + '@rolldown/binding-win32-x64-msvc': 1.0.3 + + saxen@11.0.2: {} + + scule@1.3.0: {} + + source-map-js@1.2.1: {} + + speakingurl@14.0.1: {} + + superjson@2.2.6: + dependencies: + copy-anything: 4.0.5 + + tiny-svg@4.1.4: {} + + tinyglobby@0.2.17: + dependencies: + fdir: 6.5.0(picomatch@4.0.4) + picomatch: 4.0.4 + + tslib@2.8.1: + optional: true + + typescript@6.0.3: {} + + ufo@1.6.4: {} + + undici-types@7.18.2: {} + + unplugin-utils@0.3.1: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.4 + + unplugin@3.0.0: + dependencies: + '@jridgewell/remapping': 2.3.5 + picomatch: 4.0.4 + webpack-virtual-modules: 0.6.2 + + vite@8.0.16(@types/node@24.13.2)(yaml@2.9.0): + dependencies: + lightningcss: 1.32.0 + picomatch: 4.0.4 + postcss: 8.5.15 + rolldown: 1.0.3 + tinyglobby: 0.2.17 + optionalDependencies: + '@types/node': 24.13.2 + fsevents: 2.3.3 + yaml: 2.9.0 + + vscode-uri@3.1.0: {} + + vue-component-type-helpers@3.3.4: {} + + vue-router@5.1.0(@vue/compiler-sfc@3.5.38)(pinia@3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)))(vite@8.0.16(@types/node@24.13.2)(yaml@2.9.0))(vue@3.5.38(typescript@6.0.3)): + dependencies: + '@babel/generator': 8.0.0-rc.6 + '@vue-macros/common': 3.1.2(vue@3.5.38(typescript@6.0.3)) + '@vue/devtools-api': 8.1.2 + ast-walker-scope: 0.9.0 + chokidar: 5.0.0 + json5: 2.2.3 + local-pkg: 1.2.1 + magic-string: 0.30.21 + mlly: 1.8.2 + muggle-string: 0.4.1 + pathe: 2.0.3 + picomatch: 4.0.4 + scule: 1.3.0 + tinyglobby: 0.2.17 + unplugin: 3.0.0 + unplugin-utils: 0.3.1 + vue: 3.5.38(typescript@6.0.3) + yaml: 2.9.0 + optionalDependencies: + '@vue/compiler-sfc': 3.5.38 + pinia: 3.0.4(typescript@6.0.3)(vue@3.5.38(typescript@6.0.3)) + vite: 8.0.16(@types/node@24.13.2)(yaml@2.9.0) + + vue-tsc@3.3.4(typescript@6.0.3): + dependencies: + '@volar/typescript': 2.4.28 + '@vue/language-core': 3.3.4 + typescript: 6.0.3 + + vue@3.5.38(typescript@6.0.3): + dependencies: + '@vue/compiler-dom': 3.5.38 + '@vue/compiler-sfc': 3.5.38 + '@vue/runtime-dom': 3.5.38 + '@vue/server-renderer': 3.5.38(vue@3.5.38(typescript@6.0.3)) + '@vue/shared': 3.5.38 + optionalDependencies: + typescript: 6.0.3 + + webpack-virtual-modules@0.6.2: {} + + yaml@2.9.0: {} diff --git a/public/favicon.svg b/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/public/icons.svg b/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/App.vue b/src/App.vue new file mode 100644 index 0000000..7c2aa3f --- /dev/null +++ b/src/App.vue @@ -0,0 +1,3 @@ + diff --git a/src/components/BpmnStatusViewer.vue b/src/components/BpmnStatusViewer.vue new file mode 100644 index 0000000..7f8678d --- /dev/null +++ b/src/components/BpmnStatusViewer.vue @@ -0,0 +1,326 @@ + + + + + diff --git a/src/components/GlobalAgentDrawer.vue b/src/components/GlobalAgentDrawer.vue new file mode 100644 index 0000000..eeec570 --- /dev/null +++ b/src/components/GlobalAgentDrawer.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/src/components/MetricTile.vue b/src/components/MetricTile.vue new file mode 100644 index 0000000..77bc631 --- /dev/null +++ b/src/components/MetricTile.vue @@ -0,0 +1,79 @@ + + + + + diff --git a/src/components/RunExecutionDetails.vue b/src/components/RunExecutionDetails.vue new file mode 100644 index 0000000..e5db7b8 --- /dev/null +++ b/src/components/RunExecutionDetails.vue @@ -0,0 +1,248 @@ + + + + + diff --git a/src/components/StatusPill.vue b/src/components/StatusPill.vue new file mode 100644 index 0000000..4f9030d --- /dev/null +++ b/src/components/StatusPill.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/layouts/MainLayout.vue b/src/layouts/MainLayout.vue new file mode 100644 index 0000000..38d899f --- /dev/null +++ b/src/layouts/MainLayout.vue @@ -0,0 +1,219 @@ + + + + + diff --git a/src/main.ts b/src/main.ts new file mode 100644 index 0000000..5bef187 --- /dev/null +++ b/src/main.ts @@ -0,0 +1,8 @@ +import { createApp } from 'vue' +import './style.css' +import App from './App.vue' +import ElementPlus from 'element-plus' +import { createPinia } from 'pinia' +import router from './router' + +createApp(App).use(createPinia()).use(router).use(ElementPlus).mount('#app') diff --git a/src/mocks/devops.ts b/src/mocks/devops.ts new file mode 100644 index 0000000..6797dde --- /dev/null +++ b/src/mocks/devops.ts @@ -0,0 +1,638 @@ +/** + * 本地降级数据只模拟 DevOps 运维展示,不包含任何真实 token、webhook 或服务地址。 + */ +import type { + AgentCard, + DashboardSummary, + IntegrationConfigStatus, + IntegrationStatus, + PipelineStep, + ProjectConfig, + ReleaseRun, + SystemSetting, +} from '../types/devops' + +export const releaseProcessXml = ` + + + + Flow_Start_Gitea + + + Flow_Start_Gitea + Flow_Gitea_Jenkins + + + Flow_Gitea_Jenkins + Flow_Jenkins_Health + + + Flow_Jenkins_Health + Flow_Health_Notify + + + Flow_Health_Notify + Flow_Notify_End + + + Flow_Notify_End + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +` + +export const integrations: IntegrationStatus[] = [ + { + key: 'jenkins', + name: 'Jenkins', + state: 'healthy', + summary: 'folder jobs 已按 my-project 规划,等待后端真实接入', + checkedAt: '2026-06-11 17:40', + latencyMs: 86, + }, + { + key: 'gitea', + name: 'Gitea', + state: 'healthy', + summary: '四个仓库 mock 同步完成,生产发布优先 tag', + checkedAt: '2026-06-11 17:40', + latencyMs: 42, + }, + { + key: 'wecom', + name: '通知平台', + state: 'warning', + summary: '机器人未配置,当前保留企微/飞书/通用 webhook 方案', + checkedAt: '2026-06-11 17:38', + }, + { + key: 'llm', + name: 'LLM Agent', + state: 'warning', + summary: 'baseURL/key/model 待后端提供,前端不保存密钥', + checkedAt: '2026-06-11 17:38', + }, +] + +export const integrationConfigStatus: IntegrationConfigStatus = { + checkedAt: '2026-06-11T17:40:00+08:00', + integrations: [ + { + key: 'jenkins', + name: 'Jenkins', + status: 'partial', + required: ['JENKINS_BASE_URL', 'JENKINS_USERNAME', 'JENKINS_API_TOKEN'], + configured: ['JENKINS_BASE_URL', 'JENKINS_USERNAME'], + missing: ['JENKINS_API_TOKEN'], + optional: [], + note: '前端只展示变量名,Jenkins 凭据由后端读取。', + }, + { + key: 'gitea', + name: 'Gitea', + status: 'configured', + required: ['GITEA_BASE_URL', 'GITEA_TOKEN'], + configured: ['GITEA_BASE_URL', 'GITEA_TOKEN'], + missing: [], + optional: ['GITEA_WEBHOOK_SECRET'], + note: '仓库访问统一走后端 API。', + }, + { + key: 'notification', + name: '通知平台', + status: 'missing', + required: ['NOTIFICATION_PROVIDER'], + configured: [], + missing: ['NOTIFICATION_PROVIDER'], + optional: ['WECOM_WEBHOOK_URL', 'FEISHU_WEBHOOK_URL', 'NOTIFICATION_WEBHOOK_URL'], + note: '浏览器不保存 webhook;后端按 provider 投递。', + }, + { + key: 'llm', + name: 'LLM Agent', + status: 'partial', + required: ['LLM_BASE_URL', 'LLM_API_KEY', 'LLM_MODEL'], + configured: ['LLM_MODEL'], + missing: ['LLM_BASE_URL', 'LLM_API_KEY'], + optional: [], + note: '模型供应商只由后端代理调用。', + }, + { + key: 'database', + name: 'Database', + status: 'configured', + required: ['DATABASE_URL'], + configured: ['DATABASE_URL'], + missing: [], + optional: ['USE_DATABASE_READS', 'PRISMA_CONNECT_ON_BOOT'], + note: '数据库连接仅服务端可见。', + }, + { + key: 'redis', + name: 'Redis', + status: 'configured', + required: ['REDIS_URL'], + configured: ['REDIS_URL'], + missing: [], + optional: [], + note: 'Redis 连接仅服务端可见。', + }, + { + key: 'secrets', + name: 'Secrets', + status: 'missing', + required: ['SECRET_ENCRYPTION_KEY'], + configured: [], + missing: ['SECRET_ENCRYPTION_KEY'], + optional: [], + note: '应用密钥只在后端环境中读取。', + }, + ], +} + +export const projects: ProjectConfig[] = [ + { + key: 'access-manage', + name: 'access-manage', + repository: 'https://gitea.ops.mrzhan.top/my-project/access-manage.git', + owner: 'my-project', + repo: 'access-manage', + serviceType: '后端服务', + defaultBranch: 'develop', + tagPolicy: '生产发布必须选择 v* tag', + environments: [ + { + name: 'test', + label: '测试环境', + branchPolicy: 'develop / feature/*', + status: 'success', + version: 'develop@8f31a2c', + lastRunId: 'run-1048', + lastReleasedAt: '2026-06-11 15:10', + lastActor: 'ops-admin', + jenkinsJob: 'my-project/access-manage-test', + wecomRobot: 'devops-test', + }, + { + name: 'production', + label: '生产环境', + branchPolicy: 'v* tag', + status: 'running', + version: 'v2026.06.11-api.1', + lastRunId: 'run-1052', + lastReleasedAt: '2026-06-11 17:12', + lastActor: 'ops-admin', + jenkinsJob: 'my-project/access-manage-prod', + wecomRobot: 'devops-prod', + }, + ], + integrations: { + jenkins: 'healthy', + gitea: 'healthy', + wecom: 'warning', + llm: 'warning', + }, + }, + { + key: 'role-admin', + name: 'role-admin', + repository: 'https://gitea.ops.mrzhan.top/my-project/role-admin.git', + owner: 'my-project', + repo: 'role-admin', + serviceType: '管理后台', + defaultBranch: 'develop', + tagPolicy: '生产发布必须由 develop 合并后 tag', + environments: [ + { + name: 'test', + label: '测试环境', + branchPolicy: 'develop', + status: 'success', + version: 'develop@b42e18d', + lastRunId: 'run-1043', + lastReleasedAt: '2026-06-11 14:35', + lastActor: 'frontend-worker', + jenkinsJob: 'my-project/role-admin-test', + wecomRobot: 'devops-test', + }, + { + name: 'production', + label: '生产环境', + branchPolicy: 'v* tag', + status: 'idle', + version: 'v2026.06.05-envsplit.7', + lastRunId: 'run-1026', + lastReleasedAt: '2026-06-05 16:02', + lastActor: 'ops-admin', + jenkinsJob: 'my-project/role-admin-prod', + wecomRobot: 'devops-prod', + }, + ], + integrations: { + jenkins: 'healthy', + gitea: 'healthy', + wecom: 'healthy', + llm: 'warning', + }, + }, + { + key: 'role-user', + name: 'role-user', + repository: 'https://gitea.ops.mrzhan.top/my-project/role-user.git', + owner: 'my-project', + repo: 'role-user', + serviceType: 'C 端员工应用', + defaultBranch: 'develop', + tagPolicy: '生产发布仅允许 release tag', + environments: [ + { + name: 'test', + label: '测试环境', + branchPolicy: 'develop / feature/*', + status: 'failed', + version: 'develop@4a09fd2', + lastRunId: 'run-1050', + lastReleasedAt: '2026-06-11 16:24', + lastActor: 'frontend-worker', + jenkinsJob: 'my-project/role-user-test', + wecomRobot: 'devops-test', + }, + { + name: 'production', + label: '生产环境', + branchPolicy: 'v* tag', + status: 'idle', + version: 'v2026.06.05-envsplit.7', + lastRunId: 'run-1028', + lastReleasedAt: '2026-06-05 16:08', + lastActor: 'ops-admin', + jenkinsJob: 'my-project/role-user-prod', + wecomRobot: 'devops-prod', + }, + ], + integrations: { + jenkins: 'error', + gitea: 'healthy', + wecom: 'healthy', + llm: 'warning', + }, + }, + { + key: 'my-resume', + name: 'my-resume', + repository: 'https://gitea.ops.mrzhan.top/my-project/my-resume.git', + owner: 'my-project', + repo: 'my-resume', + serviceType: '个人站点', + defaultBranch: 'master', + tagPolicy: '生产发布允许 master 或 v* tag', + environments: [ + { + name: 'test', + label: '测试环境', + branchPolicy: 'master / feature/*', + status: 'success', + version: 'master@6c92e81', + lastRunId: 'run-1039', + lastReleasedAt: '2026-06-11 12:18', + lastActor: 'frontend-worker', + jenkinsJob: 'my-project/my-resume-test', + wecomRobot: 'devops-test', + }, + { + name: 'production', + label: '生产环境', + branchPolicy: 'master 或 v* tag', + status: 'success', + version: 'v2026.06.10-resume.2', + lastRunId: 'run-1033', + lastReleasedAt: '2026-06-10 21:42', + lastActor: 'ops-admin', + jenkinsJob: 'my-project/my-resume-prod', + wecomRobot: 'devops-prod', + }, + ], + integrations: { + jenkins: 'healthy', + gitea: 'healthy', + wecom: 'healthy', + llm: 'warning', + }, + }, + { + key: 'devops-platform-api', + name: 'devops-platform-api', + repository: 'https://gitea.ops.mrzhan.top/devops-platform/devops-platform-api.git', + owner: 'devops-platform', + repo: 'devops-platform-api', + serviceType: 'DevOps 后端', + defaultBranch: 'master', + tagPolicy: '合并到 master 即发布 devops.mrzhan.top API', + environments: [ + { + name: 'production', + label: '生产环境', + branchPolicy: 'master', + status: 'idle', + version: 'master@local', + lastRunId: '-', + lastReleasedAt: '-', + lastActor: '-', + jenkinsJob: 'devops-platform/prod-devops-platform-api-master', + wecomRobot: '通知平台待配置', + }, + ], + integrations: { + jenkins: 'warning', + gitea: 'warning', + wecom: 'warning', + llm: 'warning', + }, + }, + { + key: 'devops-platform-web', + name: 'devops-platform-web', + repository: 'https://gitea.ops.mrzhan.top/devops-platform/devops-platform-web.git', + owner: 'devops-platform', + repo: 'devops-platform-web', + serviceType: 'DevOps 前端', + defaultBranch: 'master', + tagPolicy: '合并到 master 即发布 devops.mrzhan.top Web', + environments: [ + { + name: 'production', + label: '生产环境', + branchPolicy: 'master', + status: 'idle', + version: 'master@local', + lastRunId: '-', + lastReleasedAt: '-', + lastActor: '-', + jenkinsJob: 'devops-platform/prod-devops-platform-web-master', + wecomRobot: '通知平台待配置', + }, + ], + integrations: { + jenkins: 'warning', + gitea: 'warning', + wecom: 'warning', + llm: 'warning', + }, + }, +] + +const baseSteps: PipelineStep[] = [ + { + id: 'step-submit', + bpmnNodeId: 'start-release', + name: '提交发布单', + owner: 'Platform', + status: 'success', + duration: '2s', + summary: '记录操作者、项目、环境、ref 与备注。', + }, + { + id: 'step-gitea', + bpmnNodeId: 'gitea-check', + name: '校验 Gitea ref', + owner: 'Gitea', + status: 'success', + duration: '5s', + summary: '验证 ref 存在并读取最近 commit 摘要。', + }, + { + id: 'step-jenkins', + bpmnNodeId: 'jenkins-build', + name: '触发 Jenkins 构建', + owner: 'Jenkins', + status: 'running', + duration: '2m 18s', + summary: '进入 queue 后解析 build number,等待构建完成。', + }, + { + id: 'step-health', + bpmnNodeId: 'health-check', + name: '环境健康检查', + owner: 'HealthCheck', + status: 'pending', + duration: '-', + summary: '构建成功后检查服务可达性和关键接口。', + }, + { + id: 'step-wecom', + bpmnNodeId: 'notify-wecom', + name: '发送通知', + owner: 'Notification', + status: 'pending', + duration: '-', + summary: '按开始、成功、失败、取消模板投递到已配置通知平台。', + }, + { + id: 'step-archive', + bpmnNodeId: 'finish-release', + name: '归档审计', + owner: 'Platform', + status: 'pending', + duration: '-', + summary: '保存 run、step、审计日志和通知投递结果。', + }, +] + +const successSteps = baseSteps.map((step) => ({ + ...step, + status: 'success' as const, + duration: step.duration === '-' ? '3s' : step.duration, +})) + +const failedSteps: PipelineStep[] = baseSteps.map((step) => { + if (step.bpmnNodeId === 'jenkins-build') { + return { + ...step, + status: 'failed', + duration: '1m 44s', + summary: 'pnpm build 失败,mock 日志提示类型检查未通过。', + } + } + + if (['health-check', 'notify-wecom', 'finish-release'].includes(step.bpmnNodeId)) { + return { + ...step, + status: 'skipped', + summary: '上游构建失败,当前节点跳过。', + } + } + + return { + ...step, + status: 'success', + } +}) + +export const runs: ReleaseRun[] = [ + { + id: 'run-1052', + projectKey: 'access-manage', + projectName: 'access-manage', + environment: 'production', + ref: 'v2026.06.11-api.1', + status: 'running', + actor: 'ops-admin', + startedAt: '2026-06-11 17:12', + duration: '2m 25s', + note: '生产 API 修复发布,等待健康检查。', + jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/access-manage-prod/1052', + commitSummary: 'fix: stabilize access policy migration and health output', + steps: baseSteps, + }, + { + id: 'run-1050', + projectKey: 'role-user', + projectName: 'role-user', + environment: 'test', + ref: 'develop@4a09fd2', + status: 'failed', + actor: 'frontend-worker', + startedAt: '2026-06-11 16:24', + duration: '1m 56s', + note: '测试环境自动部署失败,等待诊断。', + jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/role-user-test/1050', + commitSummary: 'feat: add employee workbench filters', + steps: failedSteps, + }, + { + id: 'run-1048', + projectKey: 'access-manage', + projectName: 'access-manage', + environment: 'test', + ref: 'develop@8f31a2c', + status: 'success', + actor: 'ops-admin', + startedAt: '2026-06-11 15:10', + duration: '4m 03s', + note: '测试环境部署成功。', + jenkinsBuildUrl: 'https://jenkins.ops.mrzhan.top/job/my-project/job/access-manage-test/1048', + commitSummary: 'chore: update deployment env schema', + steps: successSteps, + }, +] + +export const agentCards: AgentCard[] = [ + { + mode: 'risk', + title: '发布风险摘要', + description: '基于 ref diff、项目配置、历史失败记录生成检查项。', + inputScope: ['项目', '环境', 'ref/tag', '最近失败 run'], + outputPreview: ['配置变更影响范围', '构建和迁移风险', '发布前人工确认项'], + }, + { + mode: 'diagnosis', + title: '失败诊断', + description: '只读取 Jenkins 日志片段、run steps 和已知 runbook。', + inputScope: ['失败 run', '失败节点', '脱敏日志片段'], + outputPreview: ['失败原因摘要', '下一步排查命令', '建议责任边界'], + }, + { + mode: 'runbook', + title: 'Runbook 问答', + description: '围绕平台文档、项目 README、历史发布记录回答运维问题。', + inputScope: ['白名单文档', '项目', '环境'], + outputPreview: ['操作步骤', '风险提醒', '需要升级给后端的事项'], + }, + { + mode: 'releaseNotes', + title: '发布说明草稿', + description: '根据 commit、PR 和 tag 生成生产发布说明草稿。', + inputScope: ['commit 列表', 'PR 信息', 'tag'], + outputPreview: ['变更摘要', '验证范围', '回滚提示'], + }, +] + +export const settings: SystemSetting[] = [ + { + key: 'api-contract', + name: 'API 契约来源', + status: 'warning', + owner: '后端 OpenAPI', + contract: '本阶段使用 mock/devops.ts,联调后切换到后端 Swagger/OpenAPI。', + boundary: '前端不写死真实 token,不直接访问 Jenkins/Gitea/LLM。', + }, + { + key: 'bpmn', + name: 'BPMN 流程定义', + status: 'healthy', + owner: '平台流程配置', + contract: 'step.bpmnNodeId 必须和 BPMN XML 节点 id 一致。', + boundary: '前端只展示流程和状态,不推进状态机。', + }, + { + key: 'agent', + name: 'Agent 边界', + status: 'warning', + owner: '后端 Agent proxy', + contract: '只支持发布风险、失败诊断、Runbook、发布说明四类用途。', + boundary: '无泛聊天入口;模型 key/baseURL/model 仅后端保存。', + }, + { + key: 'notification', + name: '通知平台', + status: 'warning', + owner: '后端 notification adapter', + contract: '通知模板由后端返回,后续可接企微、飞书或通用 webhook。', + boundary: 'webhook 不进入前端,不在浏览器保存。', + }, +] + +export const dashboardSummary: DashboardSummary = { + integrations, + projects, + runs, + agentCards, + settings, + integrationConfig: integrationConfigStatus, +} diff --git a/src/router/index.ts b/src/router/index.ts new file mode 100644 index 0000000..7ca68fe --- /dev/null +++ b/src/router/index.ts @@ -0,0 +1,84 @@ +import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' +import { useSessionStore } from '../stores/session' +import MainLayout from '../layouts/MainLayout.vue' +import LoginView from '../views/LoginView.vue' +import OverviewView from '../views/OverviewView.vue' +import ProjectsView from '../views/ProjectsView.vue' +import ReleaseCenterView from '../views/ReleaseCenterView.vue' +import RunsView from '../views/RunsView.vue' +import SettingsView from '../views/SettingsView.vue' + +const routes: RouteRecordRaw[] = [ + { + path: '/login', + name: 'login', + component: LoginView, + meta: { + public: true, + }, + }, + { + path: '/', + component: MainLayout, + redirect: '/overview', + children: [ + { + path: 'overview', + name: 'overview', + component: OverviewView, + }, + { + path: 'projects', + name: 'projects', + component: ProjectsView, + }, + { + path: 'releases', + name: 'releases', + component: ReleaseCenterView, + }, + { + path: 'runs', + name: 'runs', + component: RunsView, + }, + { + path: 'agent', + redirect: '/overview', + }, + { + path: 'settings', + name: 'settings', + component: SettingsView, + }, + ], + }, +] + +const router = createRouter({ + history: createWebHistory(), + routes, +}) + +router.beforeEach((to) => { + const session = useSessionStore() + + if (!to.meta.public && !session.isAuthenticated) { + return { + name: 'login', + query: { + redirect: to.fullPath, + }, + } + } + + if (to.name === 'login' && session.isAuthenticated) { + return { + name: 'overview', + } + } + + return true +}) + +export default router diff --git a/src/services/devopsApi.ts b/src/services/devopsApi.ts new file mode 100644 index 0000000..16cab72 --- /dev/null +++ b/src/services/devopsApi.ts @@ -0,0 +1,875 @@ +/** + * 前端唯一 DevOps API 入口;优先联调后端,失败时降级到本地 mock 保持页面可用。 + */ +import { + agentCards, + dashboardSummary, + integrationConfigStatus as mockIntegrationConfigStatus, + integrations, + projects as mockProjects, + releaseProcessXml, + runs as mockRuns, + settings, +} from '../mocks/devops' +import type { + AgentCard, + AgentInvocation, + AgentMode, + AgentPurpose, + CreateAgentInvocationPayload, + CreateDeployRunPayload, + DashboardSummary, + EnvironmentName, + HealthState, + IntegrationConfigKey, + IntegrationConfigState, + IntegrationConfigStatus, + IntegrationStatus, + JenkinsSyncSummary, + PipelineStep, + ProjectConfig, + ProjectRefs, + ReleaseRun, + ReleaseStatus, + StepStatus, + SystemSetting, +} from '../types/devops' +import { http } from './http' + +type DataSource = 'backend' | 'mock' +type BackendHealthStatus = 'ok' | 'not_configured' | 'unavailable' +type BackendRunStatus = 'pending' | 'queued' | 'running' | 'success' | 'failed' | 'canceled' + +interface BackendHealth { + status: BackendHealthStatus + checkedAt: string + dependencies: Record +} + +interface BackendProject { + id: string + key: string + name: string + repositoryUrl: string + defaultBranch: string + status: 'active' | 'archived' + environments: Array<{ + name: EnvironmentName + displayName: string + jenkinsJobPath: string + }> +} + +interface BackendDeployRunStep { + id?: string + bpmnNodeId?: string + name?: string + status?: BackendRunStatus + order?: number + startedAt?: string + finishedAt?: string + message?: string + errorSummary?: string + logExcerpt?: string +} + +interface BackendDeployRun { + id?: string + projectKey?: string + environment?: EnvironmentName + ref?: string + status?: BackendRunStatus + trigger?: 'manual' | 'gitea_webhook' | 'retry' + operator?: string + jenkinsQueueId?: string | number + jenkinsBuildNumber?: number + jenkinsBuildUrl?: string + startedAt?: string + finishedAt?: string + createdAt?: string + steps?: BackendDeployRunStep[] +} + +interface BackendJenkinsSyncSummary { + checkedAt: string + totalCandidates: number + syncedRuns: BackendDeployRun[] + failedRuns: Array<{ + id: string + projectKey: string + message: string + }> +} + +interface BackendProcessDefinition { + key: 'release' + version: string + xml: string + nodeIds: string[] +} + +let lastDataSource: DataSource = 'mock' + +const wait = async () => new Promise((resolve) => window.setTimeout(resolve, 180)) +const integrationConfigKeys: IntegrationConfigKey[] = [ + 'jenkins', + 'gitea', + 'notification', + 'wecom', + 'llm', + 'database', + 'redis', + 'secrets', +] +const redactedVariableName = '非变量名内容已隐藏' + +export const devopsApi = { + getLastDataSource(): DataSource { + return lastDataSource + }, + + async getDashboard(): Promise { + return withBackendFallback( + async () => { + const [health, backendProjects, backendRuns, integrationConfig] = await Promise.all([ + fetchHealth(), + fetchProjects(), + fetchRuns(), + fetchIntegrationConfigStatusWithFallback(), + ]) + const adaptedProjects = toProjectConfigs(backendProjects) + + return { + integrations: toIntegrationStatuses(health), + projects: adaptedProjects, + runs: backendRuns.map((run) => toReleaseRun(run, adaptedProjects)), + agentCards, + settings: toSystemSettings(health, integrationConfig), + integrationConfig, + } + }, + () => dashboardSummary, + ) + }, + + async getIntegrations(): Promise { + return withBackendFallback( + async () => toIntegrationStatuses(await fetchHealth()), + () => integrations, + ) + }, + + async getProjects(): Promise { + return withBackendFallback( + async () => toProjectConfigs(await fetchProjects()), + () => mockProjects, + ) + }, + + async getProjectRefs(projectKey: string, projectList = mockProjects): Promise { + return withBackendFallback( + async () => { + const { data } = await http.get(`/projects/${projectKey}/refs`) + return data + }, + () => createFallbackRefs(projectKey, projectList), + ) + }, + + async getRuns(projectList = mockProjects): Promise { + return withBackendFallback( + async () => (await fetchRuns()).map((run) => toReleaseRun(run, projectList)), + () => mockRuns, + ) + }, + + async createDeployRun( + payload: CreateDeployRunPayload, + projectList = mockProjects, + ): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post('/deploy-runs', payload) + return toReleaseRun(data, projectList) + }, + () => createFallbackRun(payload, projectList, 'queued'), + ) + }, + + async cancelDeployRun(runId: string, source?: ReleaseRun): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post(`/deploy-runs/${runId}/cancel`) + return toReleaseRun(data, mockProjects) + }, + () => ({ + ...(source ?? mockRuns[0]), + id: source?.id ?? runId, + status: 'canceled', + duration: source?.duration ?? '-', + steps: (source?.steps ?? mockRuns[0].steps).map((step) => ({ + ...step, + status: step.status === 'success' ? 'success' : 'canceled', + summary: step.status === 'success' ? step.summary : '人工取消,本节点未继续执行。', + })), + }), + ) + }, + + async retryDeployRun(runId: string, source?: ReleaseRun): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post(`/deploy-runs/${runId}/retry`) + return toReleaseRun(data, mockProjects) + }, + () => + createFallbackRun( + { + projectKey: source?.projectKey ?? 'access-manage', + environment: source?.environment ?? 'test', + ref: source?.ref ?? 'develop', + operator: source?.actor ?? 'ops-admin', + remark: source ? `重试 ${source.id}` : `重试 ${runId}`, + }, + mockProjects, + 'queued', + ), + ) + }, + + async syncJenkinsRun( + runId: string, + source?: ReleaseRun, + projectList = mockProjects, + ): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post(`/deploy-runs/${runId}/sync-jenkins`) + return toReleaseRun(data, projectList) + }, + () => source ?? mockRuns.find((run) => run.id === runId) ?? mockRuns[0], + ) + }, + + async syncJenkinsRuns(projectList = mockProjects): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post( + '/deploy-runs/sync-jenkins', + ) + return toJenkinsSyncSummary(data, projectList) + }, + () => ({ + checkedAt: new Date().toISOString(), + totalCandidates: 0, + syncedRuns: [], + failedRuns: [], + }), + ) + }, + + async getAgentCards(): Promise { + await wait() + return agentCards + }, + + async getIntegrationConfigStatus(): Promise { + return withBackendFallback( + async () => fetchIntegrationConfigStatus(), + () => mockIntegrationConfigStatus, + ) + }, + + async createAgentInvocation( + payload: CreateAgentInvocationPayload, + ): Promise { + return withBackendFallback( + async () => { + const { data } = await http.post('/agent/invocations', payload) + return data + }, + () => createFallbackAgentInvocation(payload), + ) + }, + + async getSettings(): Promise { + return withBackendFallback( + async () => { + const [health, integrationConfig] = await Promise.all([ + fetchHealth(), + fetchIntegrationConfigStatusWithFallback(), + ]) + + return toSystemSettings(health, integrationConfig) + }, + () => settings, + ) + }, + + async getReleaseProcessXml(): Promise { + return withBackendFallback( + async () => { + const { data } = await http.get( + '/process-definitions/release', + ) + return data.xml + }, + () => releaseProcessXml, + ) + }, +} + +async function withBackendFallback( + request: () => Promise, + fallback: () => T | Promise, +): Promise { + if (import.meta.env.VITE_DEVOPS_USE_MOCK === 'true') { + lastDataSource = 'mock' + await wait() + return fallback() + } + + try { + const result = await request() + lastDataSource = 'backend' + return result + } catch { + lastDataSource = 'mock' + await wait() + return fallback() + } +} + +async function fetchHealth(): Promise { + const { data } = await http.get('/health') + return data +} + +async function fetchProjects(): Promise { + const { data } = await http.get('/projects') + return data +} + +async function fetchRuns(): Promise { + const { data } = await http.get('/deploy-runs') + return data +} + +async function fetchIntegrationConfigStatus(): Promise { + const { data } = await http.get('/settings/integration-config') + return sanitizeIntegrationConfigStatus(data) +} + +async function fetchIntegrationConfigStatusWithFallback(): Promise { + try { + return await fetchIntegrationConfigStatus() + } catch { + return mockIntegrationConfigStatus + } +} + +function toIntegrationStatuses(health: BackendHealth): IntegrationStatus[] { + return [ + toIntegrationStatus('jenkins', 'Jenkins', health), + toIntegrationStatus('gitea', 'Gitea', health), + toIntegrationStatus('wecom', '通知平台', health), + toIntegrationStatus('llm', 'LLM Agent', health), + ] +} + +function toIntegrationStatus( + key: IntegrationStatus['key'], + name: string, + health: BackendHealth, +): IntegrationStatus { + const dependency = health.dependencies[key] ?? { status: 'unavailable' as const } + + return { + key, + name, + state: toHealthState(dependency.status), + summary: dependency.message || healthMessage(dependency.status), + checkedAt: formatDateTime(health.checkedAt), + } +} + +function sanitizeIntegrationConfigStatus( + payload: IntegrationConfigStatus, +): IntegrationConfigStatus { + const integrations = Array.isArray(payload?.integrations) ? payload.integrations : [] + + return { + checkedAt: + typeof payload?.checkedAt === 'string' + ? payload.checkedAt + : new Date().toISOString(), + integrations: integrations + .filter((item) => integrationConfigKeys.includes(item.key)) + .map((item) => { + const required = sanitizeVariableNames(item.required) + const configured = sanitizeVariableNames(item.configured) + const missing = sanitizeVariableNames(item.missing) + + return { + key: item.key, + name: typeof item.name === 'string' && item.name ? item.name : item.key, + status: sanitizeIntegrationConfigState(item.status), + required, + configured, + missing, + optional: sanitizeVariableNames(item.optional), + note: sanitizeIntegrationNote(item.note), + } + }), + } +} + +function sanitizeVariableNames(values?: string[]): string[] { + if (!Array.isArray(values)) { + return [] + } + + return Array.from( + new Set( + values + .map((value) => String(value).trim()) + .filter(Boolean) + .map((value) => + /^[A-Z][A-Z0-9_]*$/.test(value) ? value : redactedVariableName, + ), + ), + ) +} + +function sanitizeIntegrationConfigState( + state: IntegrationConfigState, +): IntegrationConfigState { + return state === 'configured' || state === 'partial' || state === 'missing' + ? state + : 'missing' +} + +function sanitizeIntegrationNote(note?: string): string | undefined { + if (!note) { + return undefined + } + + return note + .replace(/https?:\/\/\S+/g, '[已隐藏地址]') + .replace(/key=[^&\s]+/g, 'key=[已隐藏]') +} + +function toProjectConfigs(backendProjects: BackendProject[]): ProjectConfig[] { + return backendProjects.map((project) => { + const mockProject = mockProjects.find((item) => item.key === project.key) + const repo = parseRepository(project.repositoryUrl, project.key) + + return { + key: project.key, + name: project.name, + repository: project.repositoryUrl, + owner: repo.owner, + repo: repo.repo, + serviceType: mockProject?.serviceType ?? '服务', + defaultBranch: project.defaultBranch, + tagPolicy: mockProject?.tagPolicy ?? '生产发布优先选择 v* tag', + environments: project.environments.map((environment) => { + const mockEnvironment = mockProject?.environments.find( + (item) => item.name === environment.name, + ) + + return { + name: environment.name, + label: environment.displayName, + branchPolicy: + mockEnvironment?.branchPolicy ?? + (environment.name === 'production' ? 'v* tag' : project.defaultBranch), + status: mockEnvironment?.status ?? 'idle', + version: mockEnvironment?.version ?? '-', + lastRunId: mockEnvironment?.lastRunId ?? '-', + lastReleasedAt: mockEnvironment?.lastReleasedAt ?? '-', + lastActor: mockEnvironment?.lastActor ?? '-', + jenkinsJob: environment.jenkinsJobPath, + wecomRobot: mockEnvironment?.wecomRobot ?? '后端通知配置', + } + }), + integrations: mockProject?.integrations ?? { + jenkins: 'warning', + gitea: 'warning', + wecom: 'warning', + llm: 'warning', + }, + } + }) +} + +function toReleaseRun(run: BackendDeployRun, projectList: ProjectConfig[]): ReleaseRun { + const projectKey = run.projectKey || 'unknown-project' + const project = projectList.find((item) => item.key === projectKey) + const ref = run.ref || '-' + const trigger = run.trigger ? triggerLabel(run.trigger) : '-' + const startedAt = run.startedAt ?? run.createdAt + + return { + id: run.id || `${projectKey}-${ref}-${run.createdAt ?? Date.now()}`, + projectKey, + projectName: project?.name ?? projectKey, + environment: toEnvironmentName(run.environment), + ref, + status: toReleaseStatus(run.status), + actor: run.operator || '-', + startedAt: formatDateTime(startedAt), + finishedAt: formatDateTime(run.finishedAt), + duration: formatDuration(startedAt, run.finishedAt, run.status), + note: trigger, + jenkinsQueueId: formatJenkinsQueueId(run.jenkinsQueueId), + jenkinsBuildNumber: run.jenkinsBuildNumber, + jenkinsBuildUrl: run.jenkinsBuildUrl, + jenkinsSummary: formatJenkinsBuild(run), + commitSummary: ref === '-' ? trigger : `${ref} · ${trigger}`, + steps: (Array.isArray(run.steps) ? run.steps : []) + .slice() + .sort((left, right) => (left.order ?? 0) - (right.order ?? 0)) + .map(toPipelineStep), + } +} + +function toJenkinsSyncSummary( + summary: BackendJenkinsSyncSummary, + projectList: ProjectConfig[], +): JenkinsSyncSummary { + return { + checkedAt: summary.checkedAt, + totalCandidates: summary.totalCandidates, + syncedRuns: (Array.isArray(summary.syncedRuns) ? summary.syncedRuns : []).map((run) => + toReleaseRun(run, projectList), + ), + failedRuns: Array.isArray(summary.failedRuns) ? summary.failedRuns : [], + } +} + +function toPipelineStep(step: BackendDeployRunStep): PipelineStep { + const bpmnNodeId = step.bpmnNodeId || step.id || 'unknown-step' + const name = step.name || bpmnNodeId + + return { + id: step.id || bpmnNodeId, + bpmnNodeId, + name, + owner: stepOwner(bpmnNodeId), + status: toStepStatus(step.status), + duration: formatDuration(step.startedAt, step.finishedAt, step.status), + summary: step.errorSummary || step.message || name, + startedAt: formatDateTime(step.startedAt), + finishedAt: formatDateTime(step.finishedAt), + message: step.message, + errorSummary: step.errorSummary, + logExcerpt: step.logExcerpt, + } +} + +function createFallbackRun( + payload: CreateDeployRunPayload, + projectList: ProjectConfig[], + status: ReleaseStatus, +): ReleaseRun { + const project = projectList.find((item) => item.key === payload.projectKey) + const now = new Date().toISOString() + + return { + id: payload.idempotencyKey || `local-${Date.now()}`, + projectKey: payload.projectKey, + projectName: project?.name ?? payload.projectKey, + environment: payload.environment, + ref: payload.ref, + status, + actor: payload.operator, + startedAt: formatDateTime(now), + duration: status === 'queued' ? '等待 Jenkins queue' : '-', + note: payload.remark || '本地降级发布单', + jenkinsSummary: '等待 Jenkins 返回', + jenkinsBuildUrl: '', + commitSummary: '本地降级:后端不可用时保留操作上下文', + steps: mockRuns[0].steps.map((step) => ({ + ...step, + status: step.bpmnNodeId === 'start-release' ? 'success' : 'pending', + })), + } +} + +function createFallbackAgentInvocation( + payload: CreateAgentInvocationPayload, +): AgentInvocation { + return { + id: `local-agent-${Date.now()}`, + runId: payload.runId, + projectKey: payload.projectKey, + type: payload.type, + status: 'success', + promptSummary: payload.promptSummary, + resultMarkdown: fallbackAgentResult(payload.type), + createdAt: new Date().toISOString(), + } +} + +function createFallbackRefs(projectKey: string, projectList: ProjectConfig[]): ProjectRefs { + const project = projectList.find((item) => item.key === projectKey) + const defaultBranch = project?.defaultBranch ?? 'develop' + + return { + source: 'mock', + branches: Array.from(new Set([defaultBranch, 'develop', 'master'])) + .filter(Boolean) + .map((name) => ({ name })), + tags: [ + { name: 'v2026.06.05-envsplit.7' }, + { name: 'v2026.06.11-api.1' }, + ], + } +} + +function toHealthState(status: BackendHealthStatus): HealthState { + if (status === 'ok') { + return 'healthy' + } + + if (status === 'not_configured') { + return 'warning' + } + + return 'error' +} + +function toReleaseStatus(status?: BackendRunStatus): ReleaseStatus { + if (status === 'pending') { + return 'queued' + } + + if ( + status === 'queued' || + status === 'running' || + status === 'success' || + status === 'failed' || + status === 'canceled' + ) { + return status + } + + return 'idle' +} + +function toStepStatus(status?: BackendRunStatus): StepStatus { + if (status === 'pending') { + return 'pending' + } + + if ( + status === 'queued' || + status === 'running' || + status === 'success' || + status === 'failed' || + status === 'canceled' + ) { + return status + } + + return 'pending' +} + +function stepOwner(nodeId: string): PipelineStep['owner'] { + if (nodeId.includes('gitea')) { + return 'Gitea' + } + + if (nodeId.includes('jenkins')) { + return 'Jenkins' + } + + if (nodeId.includes('health')) { + return 'HealthCheck' + } + + if (nodeId.includes('wecom') || nodeId.includes('notify')) { + return 'Notification' + } + + return 'Platform' +} + +function healthMessage(status: BackendHealthStatus): string { + if (status === 'ok') { + return '后端检查通过' + } + + if (status === 'not_configured') { + return '本地未配置,等待接入真实密钥' + } + + return '后端检查不可用' +} + +function triggerLabel(trigger: BackendDeployRun['trigger']): string { + if (trigger === 'gitea_webhook') { + return 'Gitea 自动触发' + } + + if (trigger === 'retry') { + return '失败后重试' + } + + return '人工触发' +} + +function formatJenkinsBuild(run: BackendDeployRun): string { + if (run.jenkinsBuildNumber !== undefined && run.jenkinsBuildNumber !== null) { + return `Jenkins build #${run.jenkinsBuildNumber}` + } + + if (run.jenkinsQueueId) { + return `Jenkins queue ${run.jenkinsQueueId}` + } + + return '等待 Jenkins 返回' +} + +function formatJenkinsQueueId(queueId?: string | number): string | undefined { + if (queueId === undefined || queueId === null || queueId === '') { + return undefined + } + + return String(queueId) +} + +function toEnvironmentName(environment?: EnvironmentName): EnvironmentName { + return environment === 'production' ? 'production' : 'test' +} + +function parseRepository(repositoryUrl: string, fallbackRepo: string) { + try { + const pathname = new URL(repositoryUrl).pathname.replace(/\.git$/, '') + const parts = pathname.split('/').filter(Boolean) + const repo = parts.at(-1) ?? fallbackRepo + const owner = parts.at(-2) ?? 'my-project' + + return { owner, repo } + } catch { + return { owner: 'my-project', repo: fallbackRepo } + } +} + +function formatDateTime(value?: string): string { + if (!value) { + return '-' + } + + const date = new Date(value) + + if (Number.isNaN(date.getTime())) { + return value + } + + return new Intl.DateTimeFormat('zh-CN', { + year: 'numeric', + month: '2-digit', + day: '2-digit', + hour: '2-digit', + minute: '2-digit', + hour12: false, + }) + .format(date) + .replace(/\//g, '-') +} + +function formatDuration( + startedAt?: string, + finishedAt?: string, + status?: BackendRunStatus, +): string { + if (!startedAt) { + return '-' + } + + if (!finishedAt && !['queued', 'running', 'pending'].includes(status ?? 'success')) { + return '-' + } + + const started = new Date(startedAt) + const finished = finishedAt ? new Date(finishedAt) : new Date() + + if (Number.isNaN(started.getTime()) || Number.isNaN(finished.getTime())) { + return '-' + } + + const seconds = Math.max(0, Math.round((finished.getTime() - started.getTime()) / 1000)) + + if (seconds < 60) { + return `${seconds}s` + } + + return `${Math.floor(seconds / 60)}m ${seconds % 60}s` +} + +function toSystemSettings( + health: BackendHealth, + integrationConfig = mockIntegrationConfigStatus, +): SystemSetting[] { + const integrationByKey = new Map( + integrationConfig.integrations.map((item) => [item.key, item]), + ) + + return settings.map((item) => { + if (item.key === 'api-contract') { + return { + ...item, + status: toHealthState(health.status), + contract: '已优先读取 devops-platform-api 的统一 envelope;后端不可用时降级到本地 mock。', + } + } + + if (item.key === 'agent') { + return { + ...item, + status: toHealthStateFromConfig(integrationByKey.get('llm')?.status), + } + } + + if (item.key === 'notification') { + const notificationConfig = integrationByKey.get('notification') ?? integrationByKey.get('wecom') + + return { + ...item, + status: toHealthStateFromConfig(notificationConfig?.status), + } + } + + return item + }) +} + +function toHealthStateFromConfig(state?: IntegrationConfigState): HealthState { + if (state === 'configured') { + return 'healthy' + } + + if (state === 'partial') { + return 'warning' + } + + return 'error' +} + +function fallbackAgentResult(type: AgentPurpose): string { + const mode: Record = { + 'release-risk': 'risk', + 'failure-diagnosis': 'diagnosis', + 'runbook-qa': 'runbook', + 'release-note': 'releaseNotes', + 'incident-review': 'diagnosis', + } + const card = agentCards.find((item) => item.mode === mode[type]) + + return [ + `${card?.title ?? 'Agent'} 本地降级结果`, + '后端或 LLM 未配置时,前端只保留 DevOps 场景输入,不保存任何模型密钥。', + ].join('\n\n') +} diff --git a/src/services/http.ts b/src/services/http.ts new file mode 100644 index 0000000..40ac822 --- /dev/null +++ b/src/services/http.ts @@ -0,0 +1,66 @@ +import axios from 'axios' + +/** + * DevOps 平台统一 HTTP 实例,只识别后端 envelope,不承接任何第三方密钥。 + */ +export const http = axios.create({ + baseURL: import.meta.env.VITE_API_BASE_URL || '/api', + timeout: 12_000, + withCredentials: true, +}) + +http.interceptors.response.use( + (response) => { + response.data = unwrapApiPayload(response.data) + return response + }, + (error) => { + const message = + error.response?.data?.error?.message || + error.response?.data?.message || + error.message || + '请求失败,请稍后重试' + + return Promise.reject(new Error(message)) + }, +) + +interface ApiEnvelope { + success: boolean + data?: T + error?: { + code: string + message: string + details?: unknown + } +} + +function unwrapApiPayload(payload: unknown): unknown { + if (!isRecord(payload)) { + return payload + } + + if (isApiEnvelope(payload)) { + const envelope = payload + + if (envelope.success) { + return envelope.data + } + + throw new Error(envelope.error?.message || '请求失败,请稍后重试') + } + + if (Object.keys(payload).length === 1 && 'data' in payload) { + return payload.data + } + + return payload +} + +function isRecord(value: unknown): value is Record { + return typeof value === 'object' && value !== null && !Array.isArray(value) +} + +function isApiEnvelope(value: unknown): value is ApiEnvelope { + return isRecord(value) && typeof value.success === 'boolean' +} diff --git a/src/stores/platform.ts b/src/stores/platform.ts new file mode 100644 index 0000000..a5d1dfc --- /dev/null +++ b/src/stores/platform.ts @@ -0,0 +1,126 @@ +import { computed, ref } from 'vue' +import { defineStore } from 'pinia' +import { devopsApi } from '../services/devopsApi' +import type { + AgentCard, + CreateDeployRunPayload, + IntegrationStatus, + IntegrationConfigStatus, + ProjectConfig, + ProjectRefs, + JenkinsSyncSummary, + ReleaseRun, + SystemSetting, +} from '../types/devops' + +/** + * 平台运行态聚合后端和 mock 降级数据,页面只消费统一后的展示模型。 + */ +export const usePlatformStore = defineStore('platform', () => { + const loading = ref(false) + const dataSource = ref<'backend' | 'mock'>('mock') + const projects = ref([]) + const runs = ref([]) + const integrations = ref([]) + const agentCards = ref([]) + const settings = ref([]) + const integrationConfig = ref({ + checkedAt: '', + integrations: [], + }) + const releaseProcessXml = ref('') + const projectRefs = ref>({}) + + const failedRuns = computed(() => runs.value.filter((run) => run.status === 'failed')) + const runningRuns = computed(() => + runs.value.filter((run) => run.status === 'queued' || run.status === 'running'), + ) + + async function loadAll() { + loading.value = true + try { + const summary = await devopsApi.getDashboard() + projects.value = summary.projects + runs.value = summary.runs + integrations.value = summary.integrations + agentCards.value = summary.agentCards + settings.value = summary.settings + integrationConfig.value = summary.integrationConfig + releaseProcessXml.value = await devopsApi.getReleaseProcessXml() + dataSource.value = devopsApi.getLastDataSource() + } finally { + loading.value = false + } + } + + async function createDeployRun(payload: CreateDeployRunPayload) { + const run = await devopsApi.createDeployRun(payload, projects.value) + upsertRun(run) + dataSource.value = devopsApi.getLastDataSource() + return run + } + + async function cancelDeployRun(run: ReleaseRun) { + const canceled = await devopsApi.cancelDeployRun(run.id, run) + upsertRun(canceled) + dataSource.value = devopsApi.getLastDataSource() + return canceled + } + + async function retryDeployRun(run: ReleaseRun) { + const retried = await devopsApi.retryDeployRun(run.id, run) + upsertRun(retried) + dataSource.value = devopsApi.getLastDataSource() + return retried + } + + async function syncJenkinsRun(run: ReleaseRun) { + const synced = await devopsApi.syncJenkinsRun(run.id, run, projects.value) + upsertRun(synced) + dataSource.value = devopsApi.getLastDataSource() + return synced + } + + async function syncJenkinsRuns(): Promise { + const result = await devopsApi.syncJenkinsRuns(projects.value) + result.syncedRuns.forEach(upsertRun) + dataSource.value = devopsApi.getLastDataSource() + return result + } + + async function loadProjectRefs(projectKey: string) { + const refs = await devopsApi.getProjectRefs(projectKey, projects.value) + projectRefs.value = { + ...projectRefs.value, + [projectKey]: refs, + } + dataSource.value = devopsApi.getLastDataSource() + return refs + } + + function upsertRun(run: ReleaseRun) { + runs.value = [run, ...runs.value.filter((item) => item.id !== run.id)] + } + + return { + loading, + dataSource, + projects, + runs, + integrations, + agentCards, + settings, + integrationConfig, + releaseProcessXml, + projectRefs, + failedRuns, + runningRuns, + loadAll, + createDeployRun, + cancelDeployRun, + retryDeployRun, + syncJenkinsRun, + syncJenkinsRuns, + loadProjectRefs, + } +}) diff --git a/src/stores/session.ts b/src/stores/session.ts new file mode 100644 index 0000000..655dc83 --- /dev/null +++ b/src/stores/session.ts @@ -0,0 +1,28 @@ +import { computed, ref } from 'vue' +import { defineStore } from 'pinia' + +export const useSessionStore = defineStore('session', () => { + const operator = ref('ops-admin') + const role = ref<'admin' | 'operator' | 'viewer'>('admin') + const token = ref('mock-session') + + const isAuthenticated = computed(() => Boolean(token.value)) + + function login(name: string) { + operator.value = name || 'ops-admin' + token.value = 'mock-session' + } + + function logout() { + token.value = '' + } + + return { + operator, + role, + token, + isAuthenticated, + login, + logout, + } +}) diff --git a/src/style.css b/src/style.css new file mode 100644 index 0000000..af8fe81 --- /dev/null +++ b/src/style.css @@ -0,0 +1,226 @@ +@import 'element-plus/dist/index.css'; + +:root { + font-family: + Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', + sans-serif; + color: #172033; + background: #f5f7fb; + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +* { + box-sizing: border-box; +} + +body { + margin: 0; + min-width: 320px; + min-height: 100vh; + background: #f5f7fb; +} + +button, +input, +textarea, +select { + font: inherit; +} + +a { + color: inherit; + text-decoration: none; +} + +#app { + min-height: 100vh; +} + +.page-shell { + display: flex; + flex-direction: column; + gap: 18px; +} + +.page-header { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 16px; +} + +.page-header h1 { + margin: 0; + color: #111827; + font-size: 24px; + font-weight: 700; + letter-spacing: 0; +} + +.page-header p { + margin: 8px 0 0; + color: #697386; + font-size: 14px; + line-height: 1.6; +} + +.panel { + border: 1px solid #e3e8f2; + border-radius: 8px; + background: #ffffff; + box-shadow: 0 1px 2px rgb(15 23 42 / 5%); +} + +.panel-header { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 16px 18px; + border-bottom: 1px solid #edf1f7; +} + +.panel-header h2 { + margin: 0; + color: #182033; + font-size: 16px; + font-weight: 700; + letter-spacing: 0; +} + +.panel-header span { + color: #748093; + font-size: 13px; +} + +.panel-body { + padding: 18px; +} + +.muted { + color: #748093; +} + +.mono { + font-family: 'SFMono-Regular', Consolas, 'Liberation Mono', monospace; +} + +.status-grid { + display: grid; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 12px; +} + +.two-column { + display: grid; + grid-template-columns: minmax(0, 1.15fr) minmax(360px, 0.85fr); + gap: 16px; + align-items: start; +} + +.three-column { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 14px; +} + +.section-grid { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 16px; +} + +.compact-list { + display: flex; + flex-direction: column; + gap: 10px; +} + +.list-row { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + padding: 12px; + border: 1px solid #edf1f7; + border-radius: 8px; + background: #fbfcff; +} + +.list-row strong { + color: #172033; + font-size: 14px; +} + +.list-row small { + display: block; + margin-top: 4px; + color: #7b8798; + font-size: 12px; +} + +.field-hint { + margin-top: 6px; + color: #7b8798; + font-size: 12px; + line-height: 1.5; +} + +.toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: 10px; +} + +.el-card, +.el-table, +.el-input__wrapper, +.el-select__wrapper, +.el-textarea__inner, +.el-button, +.el-tag { + border-radius: 8px; +} + +.el-menu { + border-right: 0; +} + +.el-table th.el-table__cell { + background: #f7f9fc; + color: #4b5565; +} + +.danger-note { + border: 1px solid #ffd9d9; + background: #fff7f7; + color: #a63232; +} + +@media (max-width: 1180px) { + .status-grid, + .three-column, + .section-grid { + grid-template-columns: repeat(2, minmax(0, 1fr)); + } + + .two-column { + grid-template-columns: 1fr; + } +} + +@media (max-width: 760px) { + .page-header { + flex-direction: column; + } + + .status-grid, + .three-column, + .section-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/types/devops.ts b/src/types/devops.ts new file mode 100644 index 0000000..a6b4139 --- /dev/null +++ b/src/types/devops.ts @@ -0,0 +1,222 @@ +/** + * DevOps 运维平台前端展示模型,所有第三方集成密钥只允许以变量名状态呈现。 + */ +export type HealthState = 'healthy' | 'warning' | 'error' | 'offline' + +export type EnvironmentName = 'test' | 'production' + +export type ReleaseStatus = + | 'queued' + | 'running' + | 'success' + | 'failed' + | 'canceled' + | 'idle' + +export type StepStatus = + | 'pending' + | 'queued' + | 'running' + | 'success' + | 'failed' + | 'skipped' + | 'canceled' + +export type AgentMode = 'risk' | 'diagnosis' | 'runbook' | 'releaseNotes' + +export type AgentPurpose = + | 'release-risk' + | 'failure-diagnosis' + | 'runbook-qa' + | 'release-note' + | 'incident-review' + +export type IntegrationConfigKey = + | 'jenkins' + | 'gitea' + | 'notification' + | 'wecom' + | 'llm' + | 'database' + | 'redis' + | 'secrets' + +export type IntegrationConfigState = 'configured' | 'partial' | 'missing' + +/** + * 后端安全配置检查结果;数组字段只承载变量名,不承载任何密钥值或 URL 原文。 + */ +export interface IntegrationConfigItem { + key: IntegrationConfigKey + name: string + status: IntegrationConfigState + required: string[] + configured: string[] + missing: string[] + optional?: string[] + note?: string +} + +/** + * /settings/integration-config 的前端模型,已由 Axios envelope 解包。 + */ +export interface IntegrationConfigStatus { + checkedAt: string + integrations: IntegrationConfigItem[] +} + +export interface IntegrationStatus { + key: 'jenkins' | 'gitea' | 'wecom' | 'llm' + name: string + state: HealthState + summary: string + checkedAt: string + latencyMs?: number +} + +export interface EnvironmentStatus { + name: EnvironmentName + label: string + branchPolicy: string + status: ReleaseStatus + version: string + lastRunId: string + lastReleasedAt: string + lastActor: string + jenkinsJob: string + wecomRobot: string +} + +export interface ProjectConfig { + key: string + name: string + repository: string + owner: string + repo: string + serviceType: string + defaultBranch: string + tagPolicy: string + environments: EnvironmentStatus[] + integrations: { + jenkins: HealthState + gitea: HealthState + wecom: HealthState + llm: HealthState + } +} + +export interface ProjectRefs { + source: 'gitea' | 'fallback' | 'mock' + branches: Array<{ + name: string + commitSha?: string + }> + tags: Array<{ + name: string + commitSha?: string + }> +} + +export interface PipelineStep { + id: string + bpmnNodeId: string + name: string + owner: 'Gitea' | 'Jenkins' | 'HealthCheck' | 'Notification' | 'Platform' + status: StepStatus + duration: string + summary: string + startedAt?: string + finishedAt?: string + message?: string + errorSummary?: string + logExcerpt?: string +} + +export interface ReleaseRun { + id: string + projectKey: string + projectName: string + environment: EnvironmentName + ref: string + status: ReleaseStatus + actor: string + startedAt: string + finishedAt?: string + duration: string + note: string + jenkinsQueueId?: string + jenkinsBuildNumber?: number + jenkinsBuildUrl?: string + jenkinsSummary?: string + commitSummary: string + steps: PipelineStep[] +} + +export interface JenkinsSyncError { + id: string + projectKey: string + message: string +} + +export interface JenkinsSyncSummary { + checkedAt: string + totalCandidates: number + syncedRuns: ReleaseRun[] + failedRuns: JenkinsSyncError[] +} + +export interface AgentCard { + mode: AgentMode + title: string + description: string + inputScope: string[] + outputPreview: string[] +} + +export interface SystemSetting { + key: string + name: string + status: HealthState + owner: string + contract: string + boundary: string +} + +/** + * 首屏聚合数据,系统配置页通过 integrationConfig 展示真实安全配置状态。 + */ +export interface DashboardSummary { + integrations: IntegrationStatus[] + projects: ProjectConfig[] + runs: ReleaseRun[] + agentCards: AgentCard[] + settings: SystemSetting[] + integrationConfig: IntegrationConfigStatus +} + +export interface CreateDeployRunPayload { + projectKey: string + environment: EnvironmentName + ref: string + operator: string + remark?: string + idempotencyKey?: string +} + +export interface AgentInvocation { + id: string + runId?: string + projectKey?: string + type: AgentPurpose + status: 'pending' | 'running' | 'success' | 'failed' + promptSummary: string + resultMarkdown?: string + createdAt: string +} + +export interface CreateAgentInvocationPayload { + type: AgentPurpose + runId?: string + projectKey?: string + promptSummary: string +} diff --git a/src/views/AgentView.vue b/src/views/AgentView.vue new file mode 100644 index 0000000..de370e8 --- /dev/null +++ b/src/views/AgentView.vue @@ -0,0 +1,243 @@ + + + + + diff --git a/src/views/LoginView.vue b/src/views/LoginView.vue new file mode 100644 index 0000000..3d93592 --- /dev/null +++ b/src/views/LoginView.vue @@ -0,0 +1,116 @@ + + + + + diff --git a/src/views/OverviewView.vue b/src/views/OverviewView.vue new file mode 100644 index 0000000..d79eb22 --- /dev/null +++ b/src/views/OverviewView.vue @@ -0,0 +1,163 @@ + + + + + diff --git a/src/views/ProjectsView.vue b/src/views/ProjectsView.vue new file mode 100644 index 0000000..f6e419e --- /dev/null +++ b/src/views/ProjectsView.vue @@ -0,0 +1,125 @@ + + + + + diff --git a/src/views/ReleaseCenterView.vue b/src/views/ReleaseCenterView.vue new file mode 100644 index 0000000..ddd7360 --- /dev/null +++ b/src/views/ReleaseCenterView.vue @@ -0,0 +1,360 @@ + + + + + diff --git a/src/views/RunsView.vue b/src/views/RunsView.vue new file mode 100644 index 0000000..6b83ec5 --- /dev/null +++ b/src/views/RunsView.vue @@ -0,0 +1,143 @@ + + + diff --git a/src/views/SettingsView.vue b/src/views/SettingsView.vue new file mode 100644 index 0000000..9559f1f --- /dev/null +++ b/src/views/SettingsView.vue @@ -0,0 +1,297 @@ + + + + + diff --git a/tsconfig.app.json b/tsconfig.app.json new file mode 100644 index 0000000..5c750c5 --- /dev/null +++ b/tsconfig.app.json @@ -0,0 +1,14 @@ +{ + "extends": "@vue/tsconfig/tsconfig.dom.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "types": ["vite/client"], + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"] +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1ffef60 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] +} diff --git a/tsconfig.node.json b/tsconfig.node.json new file mode 100644 index 0000000..d3c52ea --- /dev/null +++ b/tsconfig.node.json @@ -0,0 +1,24 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "es2023", + "lib": ["ES2023"], + "module": "esnext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["vite.config.ts"] +} diff --git a/vite.config.ts b/vite.config.ts new file mode 100644 index 0000000..d325e9d --- /dev/null +++ b/vite.config.ts @@ -0,0 +1,10 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [vue()], + server: { + port: 4301, + }, +})