commit f8740d27f7243d967d2bfd0bd5cd5d37f83a247a Author: logan Date: Sat Sep 19 22:43:45 2026 +0800 feat: OpsxDB 桌面数据库客户端首版入库 含工作台/连库多引擎字段与 SSH·SSL、结果编辑、Redis 键浏览、 Tauri 桌面与 docs/落地页;排除本地构建产物与 sidecar 二进制。 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b6f11fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +node_modules/ +dist/ +build/ +target/ +.DS_Store +*.log +.env +.env.* +!.env.example +release/ +*.local +.vite/ +coverage/ +.turbo/ +.idea/ +.vscode/ +apps/server/opsxapi +apps/server/server +apps/desktop/src-tauri/gen/ +*.exe +*.dylib +*.so +Cargo.lock +!apps/desktop/src-tauri/Cargo.lock + +# local review / sync artifacts +.ui-review-*/ +_refs/ +_sync/ +opsxdb-refs.tar.gz +apps/server/bin/ +logs/ +apps/desktop/src-tauri/binaries/opsxdb-server-* diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..4679e29 --- /dev/null +++ b/Makefile @@ -0,0 +1,32 @@ +.PHONY: install web gui all server stop status build-web test desktop + +install: + npm install + npm --prefix apps/desktop install + +web: + ./run.sh web + +gui: + ./run.sh gui + +all: + ./run.sh all + +server: + ./run.sh server + +stop: + ./run.sh stop + +status: + ./run.sh status + +desktop: + ./run.sh desktop + +build-web: + npm run build:web + +test: + npm run test:core diff --git a/README.md b/README.md new file mode 100644 index 0000000..1e87108 --- /dev/null +++ b/README.md @@ -0,0 +1,61 @@ +> **产品形态:桌面 GUI(Tauri)**。`http://127.0.0.1:5174` 仅为开发壳。文档 `/docs`、官网 `/` 由本机后端提供。 + +# OpsxDB + +给运维和开发用的 **数据库客户端**(连接管理 / SQL 工作台 / AI 写 SQL)。 + +从 OpsxAPI 脚手架分叉:同一套 monorepo(web + Gin + 可选 Tauri),产品面换成 DB。 + +## 架构 + +``` +packages/ui 连接选择器 + SQL 工作台 + AI 面板 +packages/core 共享工具(保留) +packages/transport 浏览器 / Gin / Tauri(保留兼容) +apps/web Vite + React(:5174) +apps/desktop Tauri 2 壳 +apps/server Gin:health + MySQL 驱动 + AI stub(:8788) +``` + +## 怎么跑 + +```bash +export PATH="$HOME/.nvm/versions/node/v24.19.0/bin:/opt/homebrew/bin:$PATH" +export OPSXDB_SERVER_PORT=8788 OPSXDB_WEB_PORT=5174 + +npm install +./run.sh web # Gin :8788 + Vite :5174 +``` + +打开 http://127.0.0.1:5174/ + +```bash +curl -s http://127.0.0.1:8788/api/health +curl -s -X POST http://127.0.0.1:8788/api/ai/chat \ + -H 'Content-Type: application/json' \ + -d '{"action":"generate","prompt":"查最近用户"}' +``` + +## 日常怎么用 + +1. **新建连接** → 选类型,或粘贴 `mysql://user:pass@host:3306/db` 点「填入」 +2. **测试** → **保存并连接** +3. 左侧点库展开表;**点表名**打开前 100 行(展开表看列) +4. 编辑器 **⌘/Ctrl+Enter** 执行当前语句或选区(不是整页脚本) +5. 结果:**复制 / CSV / INSERT**;双击单元格复制 + +## 已做(便捷向) + +- 连接选择器:搜索 / 分类 / 最近 / 图标网格;URL 拆字段;按驱动表单(SQLite 文件、Redis、Oracle Service、SSL) +- 工作台:懒加载对象树、点表看数据、CodeMirror SQL、多 Tab、可拖分栏 +- 会话:连接后查询只带 `sessionId`,账密不再每次上送;本机密码做了混淆 +- 结果截断提示、危险 SQL 确认、查询历史、主题/Tab 持久化、快捷键 +- 驱动:MySQL 族 / PostgreSQL 族(含 GaussDB、人大金仓)/ SQL Server / Oracle / SQLite / Redis +- AI 面板仍是 stub(生成/解释/优化/修复/方言/样例) + +## 端口 + +| 服务 | 端口 | 环境变量 | +|------|------|----------| +| Gin | 8788 | `OPSXDB_SERVER_PORT` / `OPSXDB_ADDR` | +| Vite | 5174 | `OPSXDB_WEB_PORT` | diff --git a/api/openapi.yaml b/api/openapi.yaml new file mode 100644 index 0000000..1f898ba --- /dev/null +++ b/api/openapi.yaml @@ -0,0 +1,61 @@ +openapi: 3.0.3 +info: + title: OpsxDB Server + version: 0.2.0 + description: Database workbench API — sessions, metadata, SQL exec +servers: + - url: http://127.0.0.1:8788 +paths: + /health: + get: + summary: Health check + responses: + "200": + description: OK + /api/health: + get: + summary: Health check + responses: + "200": + description: OK + /api/db/test-connection: + post: + summary: Ping a database without creating a session + /api/db/open: + post: + summary: Ping and create a server-side session (password stays on server) + /api/db/close: + post: + summary: Close a session + /api/db/databases: + post: + summary: List databases (sessionId or full conn) + /api/db/tables: + post: + summary: List tables in a database + /api/db/columns: + post: + summary: List columns of a table + /api/db/exec: + post: + summary: Execute SQL or Redis command + /api/ai/chat: + post: + summary: AI actions (currently stub) +components: + schemas: + ConnReq: + type: object + properties: + driver: { type: string } + host: { type: string } + port: { type: integer } + user: { type: string } + password: { type: string } + database: { type: string } + service: { type: string } + ssl: { type: boolean } + sessionId: { type: string } + sql: { type: string } + table: { type: string } + limit: { type: integer } diff --git a/apps/desktop/package-lock.json b/apps/desktop/package-lock.json new file mode 100644 index 0000000..5031bd6 --- /dev/null +++ b/apps/desktop/package-lock.json @@ -0,0 +1,220 @@ +{ + "name": "@opsxdb/desktop", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "@opsxdb/desktop", + "version": "0.1.0", + "devDependencies": { + "@tauri-apps/cli": "^2.4.0" + } + }, + "node_modules/@tauri-apps/cli": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli/-/cli-2.11.4.tgz", + "integrity": "sha512-R8xGtMpwyetawSqm9kYOuMmEqkhUbvcUy8n0aNXIxollKBLESUu5f4Fx+64hgASYm1H+jSWq6jCW6zqTnH6hqQ==", + "dev": true, + "bin": { + "tauri": "tauri.js" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + }, + "optionalDependencies": { + "@tauri-apps/cli-darwin-arm64": "2.11.4", + "@tauri-apps/cli-darwin-x64": "2.11.4", + "@tauri-apps/cli-linux-arm-gnueabihf": "2.11.4", + "@tauri-apps/cli-linux-arm64-gnu": "2.11.4", + "@tauri-apps/cli-linux-arm64-musl": "2.11.4", + "@tauri-apps/cli-linux-riscv64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-gnu": "2.11.4", + "@tauri-apps/cli-linux-x64-musl": "2.11.4", + "@tauri-apps/cli-win32-arm64-msvc": "2.11.4", + "@tauri-apps/cli-win32-ia32-msvc": "2.11.4", + "@tauri-apps/cli-win32-x64-msvc": "2.11.4" + } + }, + "node_modules/@tauri-apps/cli-darwin-arm64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-arm64/-/cli-darwin-arm64-2.11.4.tgz", + "integrity": "sha512-1ryOF3ZhpZ/nemHV5zVwBQBz9jDGKmKPvWPADOhc83ig0P4bMc2iER4NbC6r9sjeIZ6RVQ4g3RZIYvezhcl4TQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-darwin-x64": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-darwin-x64/-/cli-darwin-x64-2.11.4.tgz", + "integrity": "sha512-uFsGQAAfuyz1k/yGLmkWfkBlgKAqZfxqlHmLWx81QU27RJWfmbNHCIq8T8w1e+VClleIuZUjpHWfoE4E3DLo3A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm-gnueabihf": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm-gnueabihf/-/cli-linux-arm-gnueabihf-2.11.4.tgz", + "integrity": "sha512-IaHZn5CdBL21oUmjiVOS1ctw6Ip1O0pjp70FwOWmYz1myWe0SY96ZIj2FYf7pT0m8bI2h/hrs5ZbEXXh44/MkQ==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-gnu/-/cli-linux-arm64-gnu-2.11.4.tgz", + "integrity": "sha512-N41/ukTRVe6XSuUTESuFdGeOW2i7k62tK+6gHK5Kd5/q5RPvvi19GaWAVPPb9u95HSGmTChSolBfzynUsssFaA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-arm64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-arm64-musl/-/cli-linux-arm64-musl-2.11.4.tgz", + "integrity": "sha512-v277UnT/fB64xAfSroL5N3Km3tLmvATWqJJw/wRI+g6o+HkeD0slyE7gOhNs1MbjE41R7bQOTxMVoL3aomUJmw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-riscv64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-riscv64-gnu/-/cli-linux-riscv64-gnu-2.11.4.tgz", + "integrity": "sha512-qqgNkQ2u1yZHxjhxsZaxUtRDW8dIqIYm33rx/mzwQv0SfY9x1B+iraj8vWeFiXjjSVVhEMepXSOts1TqPzvXNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-gnu": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-gnu/-/cli-linux-x64-gnu-2.11.4.tgz", + "integrity": "sha512-2VRNWl84FOH0m2giiDkO2h0QXlcMJeX+zJDpI5kDIQAx6s+geF3v48F4DXfJez4GS/FdoDGnPnw1C2iYGbQ7bQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-linux-x64-musl": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-linux-x64-musl/-/cli-linux-x64-musl-2.11.4.tgz", + "integrity": "sha512-o9GyhYor/nc7xarmwDE3ka2szuW3uuZzXjHWh64Q8YX5AtSgxdQkFWzrY4O8KiGtVNvFBI14H3Q49Qj5TOIP/A==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-arm64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-arm64-msvc/-/cli-win32-arm64-msvc-2.11.4.tgz", + "integrity": "sha512-ld5Ehb598m0VkYyylRPNeCFsBe/km0jxis6KgMpl3IGY6I/i1RwQXO05I1AsXUXO2WC6AvB/Lw4qTf/asiuEiQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-ia32-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-ia32-msvc/-/cli-win32-ia32-msvc-2.11.4.tgz", + "integrity": "sha512-12Hxi0XX/H5VFxO/bGgHkFWhml9VMgEOu9CidjeCeTNQ1l6fpUlbiGgSP7CLI3PFtW9/FfbeHieZ+kyWK5H7CA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tauri-apps/cli-win32-x64-msvc": { + "version": "2.11.4", + "resolved": "https://registry.npmjs.org/@tauri-apps/cli-win32-x64-msvc/-/cli-win32-x64-msvc-2.11.4.tgz", + "integrity": "sha512-+vDiqBIU5dMISg/wNvX3sF+ZHfgJGJ5T0AcO+EHNXV9GGAG+P5fzodlDXD3QdKCRgZxMoCm5PPvj3BqLNjBthw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + } + } +} diff --git a/apps/desktop/package.json b/apps/desktop/package.json new file mode 100644 index 0000000..8a6d833 --- /dev/null +++ b/apps/desktop/package.json @@ -0,0 +1,11 @@ +{ + "name": "@opsxdb/desktop", + "version": "0.1.0", + "private": true, + "scripts": { + "tauri": "tauri" + }, + "devDependencies": { + "@tauri-apps/cli": "^2.4.0" + } +} diff --git a/apps/desktop/src-tauri/Cargo.lock b/apps/desktop/src-tauri/Cargo.lock new file mode 100644 index 0000000..3b5714f --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.lock @@ -0,0 +1,4811 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + +[[package]] +name = "aho-corasick" +version = "1.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c982642fa9e8606056828ee9a8505737230110bb1099153c79efe865c59d12ba" +dependencies = [ + "memchr", +] + +[[package]] +name = "alloc-no-stdlib" +version = "2.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc7bb162ec39d46ab1ca8c77bf72e890535becd1751bb45f64c597edb4c8c6b3" + +[[package]] +name = "alloc-stdlib" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e76a019e91224d279006ff972f1e984179a6e9feb050adba6ce8274aef23195" +dependencies = [ + "alloc-no-stdlib", +] + +[[package]] +name = "android_system_properties" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae221649c9976a6f6c56ae1facf410f3ddb33cc661c4b7b61020a912d4237fbc" +dependencies = [ + "libc", +] + +[[package]] +name = "anyhow" +version = "1.0.104" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "330a5ed07fa54e4702c9d6c4174f74427fc0ef6e214bbd677ae50a5099946470" + +[[package]] +name = "atk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "241b621213072e993be4f6f3a9e4b45f65b7e6faad43001be957184b7bb1824b" +dependencies = [ + "atk-sys", + "glib", + "libc", +] + +[[package]] +name = "atk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e48b684b0ca77d2bbadeef17424c2ea3c897d44d566a1617e7e8f30614d086" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "autocfg" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53" + +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + +[[package]] +name = "base64" +version = "0.23.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" + +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "bitflags" +version = "2.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" +dependencies = [ + "serde_core", +] + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "brotli" +version = "8.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cc91aac060a7a1e25823bdccbfb6af1875b88f17c6daac97894eed8207166b3" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", + "brotli-decompressor", +] + +[[package]] +name = "brotli-decompressor" +version = "5.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a32acac15fe1967bc3986b2a6347dffc965602354ea6f450ad07e8bfd253583" +dependencies = [ + "alloc-no-stdlib", + "alloc-stdlib", +] + +[[package]] +name = "bs58" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf88ba1141d185c399bee5288d850d63b8369520c1eafc32a0430b5b6c287bf4" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95832e849adfb21180ccb6826a99da14e5d266ae5c2e668e1602cf234f153797" + +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" +dependencies = [ + "serde", +] + +[[package]] +name = "cairo-rs" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ca26ef0159422fb77631dc9d17b102f253b876fe1586b03b803e63a309b4ee2" +dependencies = [ + "bitflags 2.13.2", + "cairo-sys-rs", + "glib", + "libc", + "once_cell", + "thiserror 1.0.69", +] + +[[package]] +name = "cairo-sys-rs" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "685c9fa8e590b8b3d678873528d83411db17242a73fccaed827770ea0fedda51" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "camino" +version = "1.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbbad30e4b4c14a39e3cc8aed085a12a327257c316619c93581e017bc52be591" +dependencies = [ + "serde_core", +] + +[[package]] +name = "cargo-platform" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e35af189006b9c0f00a064685c727031e3ed2d8020f7ba284d78cc2671bd36ea" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.19.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd5eb614ed4c27c5d706420e4320fbe3216ab31fa1c33cd8246ac36dae4479ba" +dependencies = [ + "camino", + "cargo-platform", + "semver", + "serde", + "serde_json", + "thiserror 2.0.20", +] + +[[package]] +name = "cargo_toml" +version = "0.22.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "374b7c592d9c00c1f4972ea58390ac6b18cbb6ab79011f3bdc90a0b82ca06b77" +dependencies = [ + "serde", + "toml 0.9.12+spec-1.1.0", +] + +[[package]] +name = "cc" +version = "1.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3eb0f42d6c360dc3f8a821f6bf2fdea7f72bfd36b3076eb0e6d1e9e0752fff4" +dependencies = [ + "find-msvc-tools", + "shlex", +] + +[[package]] +name = "cesu8" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" + +[[package]] +name = "cfb" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d38f2da7a0a2c4ccf0065be06397cc26a81f4e528be095826eee9d4adbb8c60f" +dependencies = [ + "byteorder", + "fnv", + "uuid", +] + +[[package]] +name = "cfg-expr" +version = "0.15.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d067ad48b8650848b989a59a86c6c36a995d02d2bf778d45c3c5d57bc2718f02" +dependencies = [ + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "chacha20" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65c35e4b699c7e15ccbe7ee35c005e4fc0a278d22238a2857e6ce2dadeda1b06" +dependencies = [ + "cfg-if", + "cpufeatures 0.3.1", + "rand_core", +] + +[[package]] +name = "chrono" +version = "0.4.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1aa79e62e7697b8e29b513a68abacf485adcd1fe8284a4316c5ae868e6633327" +dependencies = [ + "iana-time-zone", + "num-traits", + "serde", + "windows-link 0.2.1", +] + +[[package]] +name = "combine" +version = "4.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfc320937d09e6de266b31b9afb480f197d7a861be86be7cb2ea7e5d1bfffc5e" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "cookie" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a373e3602691c3cdea496d2f0ee5935151e6168fe87739483c463db1b2f2f87" +dependencies = [ + "time", + "version_check", +] + +[[package]] +name = "core-foundation" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2a6cd9ae233e7f62ba4e9353e81a88df7fc8a5987b8d445b4d90c879bd156f6" +dependencies = [ + "core-foundation-sys", + "libc", +] + +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + +[[package]] +name = "core-graphics" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "064badf302c3194842cf2c5d61f56cc88e54a759313879cdf03abdd27d0c3b97" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "core-graphics-types", + "foreign-types", + "libc", +] + +[[package]] +name = "core-graphics-types" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" +dependencies = [ + "bitflags 2.13.2", + "core-foundation", + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "cpufeatures" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ca28b0ae3115b884660db4118d803791fd6756b6e88f39c0f3f7859060d7566" +dependencies = [ + "libc", +] + +[[package]] +name = "crc32fast" +version = "1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a7799fd6b852db0e61728dde9a204c423b44d689dbd432522543614b490e78" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "98b0cc327b5bc766e7fda9c9260cc0fa81b43a8e240440422dff70788e3f9ef1" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31eee39dddec8330830986fcd7625edb5a24ec90ea038215273bbc3adb08ac6" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cssparser" +version = "0.36.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dae61cf9c0abb83bd659dab65b7e4e38d8236824c85f0f804f173567bda257d2" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa", + "phf", + "smallvec", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13b588ba4ac1a99f7f2964d24b3d896ddc6bf847ee3855dbd4366f058cfcd331" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "ctor" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "352d39c2f7bef1d6ad73db6f5160efcaed66d94ef8c6c573a8410c00bf909a98" +dependencies = [ + "ctor-proc-macro", + "dtor", +] + +[[package]] +name = "ctor-proc-macro" +version = "0.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52560adf09603e58c9a7ee1fe1dcb95a16927b17c127f0ac02d6e768a0e25bc1" + +[[package]] +name = "darling" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff" +dependencies = [ + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn 3.0.5", +] + +[[package]] +name = "darling_macro" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785" +dependencies = [ + "darling_core", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dbus" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ab69f03cc8c4340c9c8e315114e1658e6775a9b16a04357973aa21cec22b32e" +dependencies = [ + "libc", + "libdbus-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "defmt" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2953bfe4f93bbd20cc71198842756f77d161884c99ebbabc41d80231ded88d1" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bad9c72e7ca2137e0dc3813245a0d282fd6daad32fd800af018306a9169b5fe8" +dependencies = [ + "defmt-parser", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "defmt-parser" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10d60334b3b2e7c9d91ef8150abfb6fa4c1c39ebbcf4a81c2e346aad939fee3e" +dependencies = [ + "thiserror 2.0.20", +] + +[[package]] +name = "deranged" +version = "0.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd812cc2bc1d69d4764bd80df88b4317eaef9e773c75226407d9bc0876b211c" +dependencies = [ + "serde_core", +] + +[[package]] +name = "derive_more" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d751e9e49156b02b44f9c1815bcb94b984cdcc4396ecc32521c739452808b134" +dependencies = [ + "derive_more-impl", +] + +[[package]] +name = "derive_more-impl" +version = "2.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "799a97264921d8623a957f6c3b9011f3b5492f557bbb7a5a19b7fa6d06ba8dcb" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "syn 2.0.119", +] + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dirs" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3e8aa94d75141228480295a7d0e7feb620b1a5ad9f12bc40be62411e38cce4e" +dependencies = [ + "dirs-sys", +] + +[[package]] +name = "dirs-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e01a3366d27ee9890022452ee61b2b63a67e6f13f58900b651ff5665f0bb1fab" +dependencies = [ + "libc", + "option-ext", + "redox_users", + "windows-sys 0.61.2", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags 2.13.2", + "block2", + "libc", + "objc2", +] + +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "dlopen2" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e2c5bd4158e66d1e215c49b837e11d62f3267b30c92f1d171c4d3105e3dc4d4" +dependencies = [ + "dlopen2_derive", + "libc", + "once_cell", + "winapi", +] + +[[package]] +name = "dlopen2_derive" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbbb781877580993a8707ec48672673ec7b81eeba04cfd2310bd28c08e47c8f" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "dom_query" +version = "0.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521e380c0c8afb8d9a1e83a1822ee03556fc3e3e7dbc1fd30be14e37f9cb3f89" +dependencies = [ + "bit-set", + "cssparser", + "foldhash", + "html5ever", + "precomputed-hash", + "selectors", + "tendril", +] + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" +dependencies = [ + "serde", +] + +[[package]] +name = "dtoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c3cf4824e2d5f025c7b531afcb2325364084a16806f6d47fbc1f5fbd9960590" + +[[package]] +name = "dtoa-short" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd1511a7b6a56299bd043a9c167a6d2bfb37bf84a6dfceaba651168adfb43c87" +dependencies = [ + "dtoa", +] + +[[package]] +name = "dtor" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1057d6c64987086ff8ed0fd3fbf377a6b7d205cc7715868cd401705f715cbe4" +dependencies = [ + "dtor-proc-macro", +] + +[[package]] +name = "dtor-proc-macro" +version = "0.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f678cf4a922c215c63e0de95eb1ff08a958a81d47e485cf9da1e27bf6305cfa5" + +[[package]] +name = "dunce" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" + +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "embed-resource" +version = "3.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fbfdaacccebec3b28e4866b8973543c7647797db5ada1bdab552e48fe665fbbd" +dependencies = [ + "cc", + "memchr", + "rustc_version", + "toml 1.1.6+spec-1.1.0", + "vswhom", + "winreg", +] + +[[package]] +name = "embed_plist" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "erased-serde" +version = "0.4.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2add8a07dd6a8d93ff627029c51de145e12686fbc36ecb298ac22e74cf02dec" +dependencies = [ + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "fastrand" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da7c62ceae207dd37ea5b845da6a0696c799f85e97da1ab5b7910be3c1c80223" + +[[package]] +name = "fdeflate" +version = "0.3.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c" +dependencies = [ + "simd-adler32", +] + +[[package]] +name = "field-offset" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38e2275cc4e4fc009b0669731a1e5ab7ebf11f469eaede2bab9309a5b4d6057f" +dependencies = [ + "memoffset", + "rustc_version", +] + +[[package]] +name = "find-msvc-tools" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e0f1c7c3a72c66fd80abe965175f7523475c0489a87d3ff9d6e8c87d87a9d2d" + +[[package]] +name = "flate2" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e634e2e0ebac1ee034020da1ca582e17ffe4e0f5e985823721e168928136dcb" +dependencies = [ + "crc32fast", + "miniz_oxide 0.9.1", + "zlib-rs", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + +[[package]] +name = "foreign-types" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" +dependencies = [ + "foreign-types-macros", + "foreign-types-shared", +] + +[[package]] +name = "foreign-types-macros" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea5190182e6915eb873ddbc16e23b711b6eb1f9c00a0d0a3a91b5f6228475225" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "foreign-types-shared" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa9a19cbb55df58761df49b23516a86d432839add4af60fc256da840f66ed35b" + +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + +[[package]] +name = "futures-channel" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f9e3d69d39e4862ffed03ed071a76f9a13ba1d9109d355b0f0aa6b15e393c4" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92d699e522242e69e3003b94ecc1f960f3a5e015aa7c5d7486e65ad01dd94f5e" + +[[package]] +name = "futures-executor" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "031b47cf1a3c6cc8bc2fc76cd437f521619387907d469316e7c0bc278f1f5432" +dependencies = [ + "futures-core", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-io" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c0fa8157de1303bfffdaa1cc2a673bfffb60102f76b0ef4441659124373fed" + +[[package]] +name = "futures-macro" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fb9654ba8355388abeb8dcb4fc62f511300867002afc858860463bdd9fe0c44" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "futures-sink" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1944426bf7d03f1d14f708785e4b33efd750b36d48a157b836b3efc15ede8e1d" + +[[package]] +name = "futures-task" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd417de3d1d015fc3bfd2b1ea46dfc7bab72ef86f1cc7cc9c78e728b34a6d1fd" + +[[package]] +name = "futures-util" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d50a92467f8ba5dd6e3ee5d4bd04d73ab2e4e1c44474a0674821dfce14b79bc" +dependencies = [ + "futures-core", + "futures-io", + "futures-macro", + "futures-sink", + "futures-task", + "memchr", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gdk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9f245958c627ac99d8e529166f9823fb3b838d1d41fd2b297af3075093c2691" +dependencies = [ + "cairo-rs", + "gdk-pixbuf", + "gdk-sys", + "gio", + "glib", + "libc", + "pango", +] + +[[package]] +name = "gdk-pixbuf" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50e1f5f1b0bfb830d6ccc8066d18db35c487b1b2b1e8589b5dfe9f07e8defaec" +dependencies = [ + "gdk-pixbuf-sys", + "gio", + "glib", + "libc", + "once_cell", +] + +[[package]] +name = "gdk-pixbuf-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9839ea644ed9c97a34d129ad56d38a25e6756f99f3a88e15cd39c20629caf7" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gdk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d13f38594ac1e66619e188c6d5a1adb98d11b2fcf7894fc416ad76aa2f3f7" +dependencies = [ + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkwayland-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "140071d506d223f7572b9f09b5e155afbd77428cd5cc7af8f2694c41d98dfe69" +dependencies = [ + "gdk-sys", + "glib-sys", + "gobject-sys", + "libc", + "pkg-config", + "system-deps", +] + +[[package]] +name = "gdkx11" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3caa00e14351bebbc8183b3c36690327eb77c49abc2268dd4bd36b856db3fbfe" +dependencies = [ + "gdk", + "gdkx11-sys", + "gio", + "glib", + "libc", + "x11", +] + +[[package]] +name = "gdkx11-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e2e7445fe01ac26f11601db260dd8608fe172514eb63b3b5e261ea6b0f4428d" +dependencies = [ + "gdk-sys", + "glib-sys", + "libc", + "system-deps", + "x11", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff2abc00be7fca6ebc474524697ae276ad847ad0a6b3faa4bcb027e9a4614ad0" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "wasi", + "wasm-bindgen", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "js-sys", + "libc", + "r-efi 6.0.0", + "rand_core", + "wasm-bindgen", +] + +[[package]] +name = "gio" +version = "0.18.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4fc8f532f87b79cbc51a79748f16a6828fb784be93145a322fa14d06d354c73" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys", + "glib", + "libc", + "once_cell", + "pin-project-lite", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "gio-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37566df850baf5e4cb0dfb78af2e4b9898d817ed9263d1090a2df958c64737d2" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", + "winapi", +] + +[[package]] +name = "glib" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "233daaf6e83ae6a12a52055f568f9d7cf4671dabb78ff9560ab6da230ce00ee5" +dependencies = [ + "bitflags 2.13.2", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys", + "glib-macros", + "glib-sys", + "gobject-sys", + "libc", + "memchr", + "once_cell", + "smallvec", + "thiserror 1.0.69", +] + +[[package]] +name = "glib-macros" +version = "0.18.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb0228f477c0900c880fd78c8759b95c7636dbd7842707f49e132378aa2acdc" +dependencies = [ + "heck 0.4.1", + "proc-macro-crate 2.0.2", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "glib-sys" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063ce2eb6a8d0ea93d2bf8ba1957e78dbab6be1c2220dd3daca57d5a9d869898" +dependencies = [ + "libc", + "system-deps", +] + +[[package]] +name = "glob" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b" + +[[package]] +name = "gobject-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0850127b514d1c4a4654ead6dedadb18198999985908e6ffe4436f53c785ce44" +dependencies = [ + "glib-sys", + "libc", + "system-deps", +] + +[[package]] +name = "gtk" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd56fb197bfc42bd5d2751f4f017d44ff59fbb58140c6b49f9b3b2bdab08506a" +dependencies = [ + "atk", + "cairo-rs", + "field-offset", + "futures-channel", + "gdk", + "gdk-pixbuf", + "gio", + "glib", + "gtk-sys", + "gtk3-macros", + "libc", + "pango", + "pkg-config", +] + +[[package]] +name = "gtk-sys" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f29a1c21c59553eb7dd40e918be54dccd60c52b049b75119d5d96ce6b624414" +dependencies = [ + "atk-sys", + "cairo-sys-rs", + "gdk-pixbuf-sys", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "pango-sys", + "system-deps", +] + +[[package]] +name = "gtk3-macros" +version = "0.18.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ff3c5b21f14f0736fed6dcfc0bfb4225ebf5725f3c0209edeec181e4d73e9d" +dependencies = [ + "proc-macro-crate 1.3.1", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "hex" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f24254aa9a54b5c858eaee2f5bccdb46aaf0e486a595ed5fd8f86ba55232a70" + +[[package]] +name = "html5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1054432bae2f14e0061e33d23402fbaa67a921d319d56adc6bcf887ddad1cbc2" +dependencies = [ + "log", + "markup5ever", +] + +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "hyper" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b501faa50e7a26c3d3560ca625132f4078a17771f4810baf70475ae48cbe43" +dependencies = [ + "atomic-waker", + "bytes", + "futures-channel", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-rustls" +version = "0.27.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ca68d021ef39cf6463ab54c1d0f5daf03377b70561305bb89a8f83aab66e0f" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "tokio", + "tokio-rustls", + "tower-service", + "webpki-roots", +] + +[[package]] +name = "hyper-util" +version = "0.1.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96547c2556ec9d12fb1578c4eaf448b04993e7fb79cbaad930a656880a6bdfa0" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "iana-time-zone" +version = "0.1.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e31bc9ad994ba00e440a8aa5c9ef0ec67d5cb5e5cb0cc7f8b744a35b389cc470" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "log", + "wasm-bindgen", + "windows-core 0.62.2", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ico" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e795dff5605e0f04bff85ca41b51a96b83e80b281e96231bcaaf1ac35103371" +dependencies = [ + "byteorder", + "png 0.17.16", +] + +[[package]] +name = "icu_collections" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa68d21081c4a05d5a901a1c62add574c77048b6a1c67be3b50ce0b60d4ca513" +dependencies = [ + "displaydoc", + "potential_utf", + "utf8_iter", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locale_core" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d56e28588da92eee5c3201a6eff33fabdd49b62269c8938d4ff050ce4d900deb" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_normalizer" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12f9cf5f235641ed274641dd81c3f28d870e276763d0797aeeab72317b1c646f" +dependencies = [ + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1563da1ed3e0b3bf3d74c9b85917ac9c56464d2f57242270c09c9e752f8021a0" + +[[package]] +name = "icu_properties" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e7ca276ad3145661a65914e6daf131ca5120cd3dcee8f8f3214b8875184a148" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locale_core", + "icu_properties_data", + "icu_provider", + "zerotrie", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "2.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e590f038c1464a96894fd6d10127e90a8be4509f56ff7ecef851b15cee0b7caa" + +[[package]] +name = "icu_provider" +version = "2.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d27bbb9d3abbefac45d55f647c9de1d44aafcd1186eb91879afef17c396c3e73" +dependencies = [ + "displaydoc", + "icu_locale_core", + "writeable", + "yoke", + "zerofrom", + "zerotrie", + "zerovec", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb68373c0d6620ef8105e855e7745e18b0d00d3bdb07fb532e434244cdb9a714" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + +[[package]] +name = "indexmap" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" +dependencies = [ + "autocfg", + "hashbrown 0.12.3", + "serde", +] + +[[package]] +name = "indexmap" +version = "2.14.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc4e190f5d26ca7051642629da2c52fc03bde85a03197c99408dcd291734c855" +dependencies = [ + "equivalent", + "hashbrown 0.17.1", + "serde", + "serde_core", +] + +[[package]] +name = "infer" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a588916bfdfd92e71cacef98a63d9b1f0d74d6599980d11894290e7ddefffcf7" +dependencies = [ + "cfb", +] + +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + +[[package]] +name = "javascriptcore-rs" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca5671e9ffce8ffba57afc24070e906da7fc4b1ba66f2cabebf61bf2ea257fcc" +dependencies = [ + "bitflags 1.3.2", + "glib", + "javascriptcore-rs-sys", +] + +[[package]] +name = "javascriptcore-rs-sys" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af1be78d14ffa4b75b66df31840478fef72b51f8c2465d4ca7c194da9f7a5124" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "jiff" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ab1baf72f08796de0260609515130699b890ac25f30e610ad894bc5856cafdb" +dependencies = [ + "defmt", + "jiff-core", + "jiff-static", + "jiff-tzdb-platform", + "log", + "portable-atomic", + "portable-atomic-util", + "serde_core", + "windows-link 0.2.1", +] + +[[package]] +name = "jiff-core" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e52fe76043ccecc9005d2305ebaadf7d7fc0cc89ca6baa10a94d6bc68c7128c" +dependencies = [ + "defmt", + "log", +] + +[[package]] +name = "jiff-static" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "378268a1116ad67ae6228701118ac9f491d78fda38a40a1f1a9e1348de6f7212" +dependencies = [ + "jiff-core", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jiff-tzdb" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "142bd39932ad231f10513df9ab62661fead8719872150b7ad02a2df79f4e141e" + +[[package]] +name = "jiff-tzdb-platform" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" +dependencies = [ + "jiff-tzdb", +] + +[[package]] +name = "jni" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a87aa2bb7d2af34197c04845522473242e1aa17c12f4935d5856491a7fb8c97" +dependencies = [ + "cesu8", + "cfg-if", + "combine", + "jni-sys 0.3.1", + "log", + "thiserror 1.0.69", + "walkdir", + "windows-sys 0.45.0", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "js-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce57d20d1ea864ce2ac172ab472d409214f4fd359f0b2a2775abdf522e2af99e" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "json-patch" +version = "3.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "863726d7afb6bc2590eeff7135d923545e5e964f004c2ccf8716c25e70a86f08" +dependencies = [ + "jsonptr", + "serde", + "serde_json", + "thiserror 1.0.69", +] + +[[package]] +name = "jsonptr" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dea2b27dd239b2556ed7a25ba842fe47fd602e7fc7433c2a8d6106d4d9edd70" +dependencies = [ + "serde", + "serde_json", +] + +[[package]] +name = "keyboard-types" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b750dcadc39a09dbadd74e118f6dd6598df77fa01df0cfcdc52c28dece74528a" +dependencies = [ + "bitflags 2.13.2", + "serde", + "unicode-segmentation", +] + +[[package]] +name = "libappindicator" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03589b9607c868cc7ae54c0b2a22c8dc03dd41692d48f2d7df73615c6a95dc0a" +dependencies = [ + "glib", + "gtk", + "gtk-sys", + "libappindicator-sys", + "log", +] + +[[package]] +name = "libappindicator-sys" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9ec52138abedcc58dc17a7c6c0c00a2bdb4f3427c7f63fa97fd0d859155caf" +dependencies = [ + "gtk-sys", + "libloading", + "once_cell", +] + +[[package]] +name = "libc" +version = "0.2.189" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3eaf3ede3fee6db1a4c2ee091bf8a8b4dccdc6d17f656fb07896ee72867612f2" + +[[package]] +name = "libdbus-sys" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "328c4789d42200f1eeec05bd86c9c13c7f091d2ba9a6ea35acdf51f31bc0f043" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libloading" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" +dependencies = [ + "cfg-if", + "winapi", +] + +[[package]] +name = "libredox" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6480ccc157a1389bb2e4891b24751b0f798ba640d22386f23143fbcc89da195a" +dependencies = [ + "libc", +] + +[[package]] +name = "litemap" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d9d19d1d6efa0109d2f65ff4c85cddd50bd572e5a00127ab10987290bcefae" + +[[package]] +name = "lock_api" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "224399e74b87b5f3557511d98dff8b14089b3dadafcab6bb93eab67d3aace965" +dependencies = [ + "scopeguard", +] + +[[package]] +name = "log" +version = "0.4.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" + +[[package]] +name = "lru-slab" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4050469837a6ff301cd14c1f8f24f88549e6d548f24f64e2148eb0f72cebc51f" + +[[package]] +name = "markup5ever" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8983d30f2915feeaaab2d6babdd6bc7e9ed1a00b66b5e6d74df19aa9c0e91862" +dependencies = [ + "log", + "tendril", + "web_atoms", +] + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memoffset" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" +dependencies = [ + "autocfg", +] + +[[package]] +name = "mime" +version = "0.3.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "miniz_oxide" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b63fbc4a50860e98e7b2aa7804ded1db5cbc3aff9193adaff57a6931bf7c4b4c" +dependencies = [ + "adler2", + "simd-adler32", +] + +[[package]] +name = "mio" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b18443e9c262bfe8fa82f51666e2642c53393f7e5c27b3e1aeab922cff5b9d8" +dependencies = [ + "libc", + "wasi", + "windows-sys 0.61.2", +] + +[[package]] +name = "muda" +version = "0.19.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1dd04e60bc0b07438a6771710ee1698f98f6ebbc7f89b61264af1563b8aeb878" +dependencies = [ + "crossbeam-channel", + "dpi", + "gtk", + "keyboard-types", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags 2.13.2", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "new_debug_unreachable" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" + +[[package]] +name = "num-conv" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "521739c6d2bac4aa25192232afe6841231376b2b26d4d9fae5ecf8ca5772e441" + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate 3.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", + "objc2-exception-helper", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-cloud-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73ad74d880bb43877038da939b7427bba67e9dd42004a18b809ba7d87cee241c" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-data" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b402a653efbb5e82ce4df10683b6b28027616a2715e90009947d50b8dd298fa" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags 2.13.2", + "dispatch2", + "objc2", + "objc2-core-foundation", + "objc2-io-surface", +] + +[[package]] +name = "objc2-core-image" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5d563b38d2b97209f8e861173de434bd0214cf020e3423a52624cd1d989f006" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-location" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca347214e24bc973fc025fd0d36ebb179ff30536ed1f80252706db19ee452009" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-text" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cde0dfb48d25d2b4862161a4d5fcc0e3c24367869ad306b0c9ec0073bfed92d" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-exception-helper" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7a1c5fbb72d7735b076bb47b578523aedc40f3c439bea6dfd595c089d79d98a" +dependencies = [ + "cc", +] + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-io-surface" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "180788110936d59bab6bd83b6060ffdfffb3b922ba1396b312ae795e1de9d81d" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-quartz-core" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96c1358452b371bf9f104e21ec536d37a650eb10f7ee379fff67d2e08d537f1f" +dependencies = [ + "bitflags 2.13.2", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-cloud-kit", + "objc2-core-data", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-image", + "objc2-core-location", + "objc2-core-text", + "objc2-foundation", + "objc2-quartz-core", + "objc2-user-notifications", +] + +[[package]] +name = "objc2-user-notifications" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9df9128cbbfef73cda168416ccf7f837b62737d748333bfe9ab71c245d76613e" +dependencies = [ + "objc2", + "objc2-foundation", +] + +[[package]] +name = "objc2-web-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e5aaab980c433cf470df9d7af96a7b46a9d892d521a2cbbb2f8a4c16751e7f" +dependencies = [ + "bitflags 2.13.2", + "block2", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "opsxdb-desktop" +version = "0.1.0" +dependencies = [ + "reqwest 0.12.28", + "serde", + "serde_json", + "tauri", + "tauri-build", + "tokio", + "url", +] + +[[package]] +name = "option-ext" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" + +[[package]] +name = "pango" +version = "0.18.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ca27ec1eb0457ab26f3036ea52229edbdb74dee1edd29063f5b9b010e7ebee4" +dependencies = [ + "gio", + "glib", + "libc", + "once_cell", + "pango-sys", +] + +[[package]] +name = "pango-sys" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "436737e391a843e5933d6d9aa102cb126d501e815b83601365a948a518555dc5" +dependencies = [ + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "parking_lot" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93857453250e3077bd71ff98b6a65ea6621a19bb0f559a85248955ac12c45a1a" +dependencies = [ + "lock_api", + "parking_lot_core", +] + +[[package]] +name = "parking_lot_core" +version = "0.9.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2621685985a2ebf1c516881c026032ac7deafcda1a2c9b7850dc81e3dfcb64c1" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "smallvec", + "windows-link 0.2.1", +] + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "phf" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c1562dc717473dbaa4c1f85a36410e03c047b2e7df7f45ee938fbef64ae7fadf" +dependencies = [ + "phf_macros", + "phf_shared", + "serde", +] + +[[package]] +name = "phf_codegen" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49aa7f9d80421bca176ca8dbfebe668cc7a2684708594ec9f3c0db0805d5d6e1" +dependencies = [ + "phf_generator", + "phf_shared", +] + +[[package]] +name = "phf_generator" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "135ace3a761e564ec88c03a77317a7c6b80bb7f7135ef2544dbe054243b89737" +dependencies = [ + "fastrand", + "phf_shared", +] + +[[package]] +name = "phf_macros" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "812f032b54b1e759ccd5f8b6677695d5268c588701effba24601f6932f8269ef" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "phf_shared" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57fef6bc5981e38c2ce2d63bfa546861309f875b8a75f092d1d54ae2d64f266" +dependencies = [ + "siphasher", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pkg-config" +version = "0.3.34" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6b464fbc74e149a392436b17d523f769e057cb6877f6a5c4618bc6f11800548" + +[[package]] +name = "plist" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2896bade328c13f7042a297ea5ac5b0951f6cf989dea5f32c2fd98da398195cb" +dependencies = [ + "base64 0.23.1", + "indexmap 2.14.2", + "quick-xml", + "serde", + "time", +] + +[[package]] +name = "png" +version = "0.17.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526" +dependencies = [ + "bitflags 1.3.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "png" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61" +dependencies = [ + "bitflags 2.13.2", + "crc32fast", + "fdeflate", + "flate2", + "miniz_oxide 0.8.9", +] + +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + +[[package]] +name = "portable-atomic-util" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10ab3eb7f3becc3a1cbc4f2c6f20267996cfc1a6467a873763411b136a122715" +dependencies = [ + "portable-atomic", +] + +[[package]] +name = "potential_utf" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83eb9bc6d8e5cf568e7a1101d60ee05e81ed50ea106026f3d18deeb046d7661" +dependencies = [ + "zerovec", +] + +[[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + +[[package]] +name = "proc-macro-crate" +version = "1.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" +dependencies = [ + "once_cell", + "toml_edit 0.19.15", +] + +[[package]] +name = "proc-macro-crate" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b00f26d3400549137f92511a46ac1cd8ce37cb5598a96d382381458b992a5d24" +dependencies = [ + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit 0.25.15+spec-1.1.0", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.107" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "985e7ec9bb745e6ce6535b544d84d6cd6f7ad8bd711c398938ae983b91a766d9" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quick-xml" +version = "0.42.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41b1177fdf999d2321d3fb46ff47159d9c1fb9ad66a4879f8c50a0b504615e9b" +dependencies = [ + "memchr", +] + +[[package]] +name = "quinn" +version = "0.11.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4051e23e9185c255a7e33ef59cdbca87a22d359052eecd22fc6b901fb37d9d11" +dependencies = [ + "bytes", + "cfg_aliases", + "pin-project-lite", + "quinn-proto", + "quinn-udp", + "rustc-hash", + "rustls", + "socket2", + "thiserror 2.0.20", + "tokio", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-proto" +version = "0.11.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9746dbde176634f4f2f1faf2404e30a31b2bc1e9cafb5329c95d8177a18c9fc" +dependencies = [ + "bytes", + "getrandom 0.4.3", + "lru-slab", + "rand", + "rand_pcg", + "ring", + "rustc-hash", + "rustls", + "rustls-pki-types", + "slab", + "thiserror 2.0.20", + "tinyvec", + "tracing", + "web-time", +] + +[[package]] +name = "quinn-udp" +version = "0.5.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35a133f956daabe89a61a685c2649f13d82d5aa4bd5d12d1277e1072a21c0694" +dependencies = [ + "cfg_aliases", + "libc", + "once_cell", + "socket2", + "tracing", + "windows-sys 0.61.2", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f5fa3a058cd35567ef9bfa5e75732bee0f9e4c55fa90477bef2dfcdbc4be80" +dependencies = [ + "chacha20", + "getrandom 0.4.3", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63b8176103e19a2643978565ca18b50549f6101881c443590420e4dc998a3c69" + +[[package]] +name = "rand_pcg" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "caa0f4137e1c0a72f4c651489402276c8e8e1cf081f3b0ba156d2cbeef09e86a" +dependencies = [ + "rand_core", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags 2.13.2", +] + +[[package]] +name = "redox_users" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60dc65c0ff1a7ae1294b0c67b9f14baf70b644404010370171787bfac1038fc0" +dependencies = [ + "libredox", + "thiserror 2.0.20", +] + +[[package]] +name = "ref-cast" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e440fb4e4b4147295338efb76001ab9e4efc0e5839df2c47fc5ac2381d365c3" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92ecd8964f8453721699a1ed72037b0db49ce2f5a5138486ee89bed6f67cdf3a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad8553b9b26413251cbf30e620595c7a41b3887f03da04579c0e6b0d6a06b4b2" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reqwest" +version = "0.12.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eddd3ca559203180a307f12d114c268abf583f59b03cb906fd0b3ff8646c1147" +dependencies = [ + "base64 0.22.1", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-rustls", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "quinn", + "rustls", + "rustls-pki-types", + "serde", + "serde_json", + "serde_urlencoded", + "sync_wrapper", + "tokio", + "tokio-rustls", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "webpki-roots", +] + +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64 0.23.1", + "bytes", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "serde", + "serde_json", + "sync_wrapper", + "tokio", + "tokio-util", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-streams", + "web-sys", +] + +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.17", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustls" +version = "0.23.45" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d41d731c7d2f962d1ccc364cec258de3c0e93b38c2fb3ba97ac74513048d634" +dependencies = [ + "once_cell", + "ring", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.15.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2f4925028c7eb5d1fcdaf196971378ed9d2c1c4efc7dc5d011256f76c99c0a96" +dependencies = [ + "web-time", + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c3cf1d8b1e7d4927e2d154c3fcb02979afb9939629c62cd9048d4f07b60ac2" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "ryu" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "schemars" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3fbf2ae1b8bc8e02df939598064d22402220cd5bbcca1c76f7d6a310974d5615" +dependencies = [ + "dyn-clone", + "indexmap 1.9.3", + "schemars_derive", + "serde", + "serde_json", + "url", + "uuid", +] + +[[package]] +name = "schemars" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd191f9397d57d581cddd31014772520aa448f65ef991055d7f61582c65165f" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "687274d293b6cdc6e73e0fee520bf2049650090d7164f87672d212a3c530cf4a" +dependencies = [ + "dyn-clone", + "ref-cast", + "serde", + "serde_json", +] + +[[package]] +name = "schemars_derive" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e265784ad618884abaea0600a9adf15393368d840e0222d101a072f3f7534d" +dependencies = [ + "proc-macro2", + "quote", + "serde_derive_internals", + "syn 2.0.119", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "selectors" +version = "0.36.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9c0c92a92d33f08817311cf3f2c29a3538a8240e94a6a3c622ce652d7e00c" +dependencies = [ + "bitflags 2.13.2", + "cssparser", + "derive_more", + "log", + "new_debug_unreachable", + "phf", + "phf_codegen", + "precomputed-hash", + "rustc-hash", + "servo_arc", + "smallvec", +] + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +dependencies = [ + "serde", + "serde_core", +] + +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde-untagged" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9faf48a4a2d2693be24c6289dbe26552776eb7737074e6722891fadbe6c5058" +dependencies = [ + "erased-serde", + "serde", + "serde_core", + "typeid", +] + +[[package]] +name = "serde_core" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67dca2c9c51e58a4791a4b1ed58308b39c64224d349a935ab5039aa360942a48" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7a5d71263a5a7d47b41f6b3f06ba276f10cc18b0931f1799f710578e2309348" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_derive_internals" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18d26a20a969b9e3fdf2fc2d9f21eda6c40e2de84c9408bb5d3b05d499aae711" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_repr" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d3b1629de253c70a0508c3899572da79ca359fdab27c7920ff00406df418906" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serde_spanned" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf41e0cfaf7226dca15e8197172c295a782857fcb97fad1808a166870dee75a3" +dependencies = [ + "serde", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + +[[package]] +name = "serde_urlencoded" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" +dependencies = [ + "form_urlencoded", + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "serde_with" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "935177bb8c0cd8ca1a4e6d1a2ac8988bea69cab4f9d3a31311e012ad27868ea4" +dependencies = [ + "base64 0.23.1", + "bs58", + "chrono", + "hex", + "indexmap 1.9.3", + "indexmap 2.14.2", + "jiff", + "schemars 0.9.0", + "schemars 1.2.2", + "serde_core", + "serde_json", + "serde_with_macros", + "time", +] + +[[package]] +name = "serde_with_macros" +version = "3.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d607aa01a3cb0ad757d6fd216136910db3c97b102fe686585689615a02dbcdc" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "serialize-to-javascript" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04f3666a07a197cdb77cdf306c32be9b7f598d7060d50cfd4d5aa04bfd92f6c5" +dependencies = [ + "serde", + "serde_json", + "serialize-to-javascript-impl", +] + +[[package]] +name = "serialize-to-javascript-impl" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "772ee033c0916d670af7860b6e1ef7d658a4629a6d0b4c8c3e67f09b3765b75d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "servo_arc" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "170fb83ab34de17dc69aa7c67482b22218ddb85da56546f9bd6b929e32a05930" +dependencies = [ + "stable_deref_trait", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures 0.2.17", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + +[[package]] +name = "siphasher" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba467056f1b547ed52077911161fc86985becbc60e8e1857c8a144dab0def891" + +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "softbuffer" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aac18da81ebbf05109ab275b157c22a653bb3c12cf884450179942f81bcbf6c3" +dependencies = [ + "bytemuck", + "js-sys", + "ndk", + "objc2", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "objc2-quartz-core", + "raw-window-handle", + "redox_syscall", + "tracing", + "wasm-bindgen", + "web-sys", + "windows-sys 0.61.2", +] + +[[package]] +name = "soup3" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "471f924a40f31251afc77450e781cb26d55c0b650842efafc9c6cbd2f7cc4f9f" +dependencies = [ + "futures-channel", + "gio", + "glib", + "libc", + "soup3-sys", +] + +[[package]] +name = "soup3-sys" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ebe8950a680a12f24f15ebe1bf70db7af98ad242d9db43596ad3108aab86c27" +dependencies = [ + "gio-sys", + "glib-sys", + "gobject-sys", + "libc", + "system-deps", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + +[[package]] +name = "string_cache" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18596f8c785a729f2819c0f6a7eae6ebeebdfffbfe4214ae6b087f690e31901" +dependencies = [ + "new_debug_unreachable", + "parking_lot", + "phf_shared", + "precomputed-hash", +] + +[[package]] +name = "string_cache_codegen" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "585635e46db231059f76c5849798146164652513eb9e8ab2685939dd90f29b69" +dependencies = [ + "phf_generator", + "phf_shared", + "proc-macro2", + "quote", +] + +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + +[[package]] +name = "swift-rs" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e45c444e496845d3f2a351146bff59aae4975b2280238df1dfaa0c7d1846f38e" +dependencies = [ + "base64 0.21.7", + "serde", + "serde_json", +] + +[[package]] +name = "syn" +version = "1.0.109" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" +dependencies = [ + "proc-macro2", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12df2e0110f65b775f769bb17ef989067a1d931b2eb822bd4346631eeada89f9" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "system-deps" +version = "6.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a3e535eb8dded36d55ec13eddacd30dec501792ff23a0b1682c38601b8cf2349" +dependencies = [ + "cfg-expr", + "heck 0.5.0", + "pkg-config", + "toml 0.8.2", + "version-compare", +] + +[[package]] +name = "tao" +version = "0.35.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1c93047acf68669466a34690ac58cca7010bd1b201e1ec86f1fd0a75d3dd4a9" +dependencies = [ + "bitflags 2.13.2", + "block2", + "core-foundation", + "core-graphics", + "crossbeam-channel", + "dbus", + "dispatch2", + "dlopen2", + "dpi", + "gdkwayland-sys", + "gdkx11-sys", + "gtk", + "jni", + "libc", + "log", + "ndk", + "ndk-sys", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "once_cell", + "parking_lot", + "percent-encoding", + "raw-window-handle", + "tao-macros", + "unicode-segmentation", + "url", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "tao-macros" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5f7eeb6d99155545da6150a1795945f16ac9c178deb2a5f2e74d776107bd5849" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "target-lexicon" +version = "0.12.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" + +[[package]] +name = "tauri" +version = "2.11.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "667b20e2726d572dea2de7370da16e188eb06008faf9a92fab7cdc46791190b5" +dependencies = [ + "anyhow", + "bytes", + "cookie", + "dirs", + "dunce", + "embed_plist", + "getrandom 0.3.4", + "glob", + "gtk", + "heck 0.5.0", + "http", + "jni", + "libc", + "log", + "mime", + "muda", + "objc2", + "objc2-app-kit", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "percent-encoding", + "plist", + "raw-window-handle", + "reqwest 0.13.5", + "serde", + "serde_json", + "serde_repr", + "serialize-to-javascript", + "swift-rs", + "tauri-build", + "tauri-macros", + "tauri-runtime", + "tauri-runtime-wry", + "tauri-utils", + "thiserror 2.0.20", + "tokio", + "tray-icon", + "url", + "webkit2gtk", + "webview2-com", + "window-vibrancy", + "windows", +] + +[[package]] +name = "tauri-build" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc9ce40b16101cb6ea63d3e221567affd1c3a9205f95d7bc574941a10636b632" +dependencies = [ + "anyhow", + "cargo_toml", + "dirs", + "glob", + "heck 0.5.0", + "json-patch", + "schemars 0.8.22", + "semver", + "serde", + "serde_json", + "tauri-utils", + "tauri-winres", + "walkdir", +] + +[[package]] +name = "tauri-codegen" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08279169ff42f8fc45a1dbc9dcae888893ba95288142e5880c59b93a26d2cfc5" +dependencies = [ + "base64 0.22.1", + "brotli", + "ico", + "json-patch", + "plist", + "png 0.17.16", + "proc-macro2", + "quote", + "semver", + "serde", + "serde_json", + "sha2", + "syn 2.0.119", + "tauri-utils", + "thiserror 2.0.20", + "time", + "url", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-macros" +version = "2.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8b394794f399a421811d06966343e7933fcae92d59f5180b9388d1174497a45" +dependencies = [ + "heck 0.5.0", + "proc-macro2", + "quote", + "syn 2.0.119", + "tauri-codegen", + "tauri-utils", +] + +[[package]] +name = "tauri-runtime" +version = "2.11.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0b4bc95aed361b0019067d189a1174a603d460d0f6c72606512d59fc9c12ec8" +dependencies = [ + "cookie", + "dpi", + "gtk", + "http", + "jni", + "objc2", + "objc2-ui-kit", + "objc2-web-kit", + "raw-window-handle", + "serde", + "serde_json", + "tauri-utils", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webview2-com", + "windows", +] + +[[package]] +name = "tauri-runtime-wry" +version = "2.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4e6fac707727b7a2f48e4ded90976324267371073edbb415ffb73bb0458d203f" +dependencies = [ + "gtk", + "http", + "jni", + "log", + "objc2", + "objc2-app-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "softbuffer", + "tao", + "tauri-runtime", + "tauri-utils", + "url", + "webkit2gtk", + "webview2-com", + "windows", + "wry", +] + +[[package]] +name = "tauri-utils" +version = "2.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e176a18e67764923c4f1ce66f25ae4abe5f688384d5eb1a0fa6c77f3d90f887" +dependencies = [ + "anyhow", + "brotli", + "cargo_metadata", + "ctor", + "dom_query", + "dunce", + "glob", + "http", + "infer", + "json-patch", + "log", + "memchr", + "phf", + "plist", + "proc-macro2", + "quote", + "regex", + "schemars 0.8.22", + "semver", + "serde", + "serde-untagged", + "serde_json", + "serde_with", + "swift-rs", + "thiserror 2.0.20", + "toml 1.1.6+spec-1.1.0", + "url", + "urlpattern", + "uuid", + "walkdir", +] + +[[package]] +name = "tauri-winres" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc65d45c68858bfe420dd29e834b5d15dbecf8a07a8a16cf4d532c7b1f69d4b6" +dependencies = [ + "dunce", + "embed-resource", + "toml 1.1.6+spec-1.1.0", +] + +[[package]] +name = "tendril" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5fed54709c5b3a53d09bb1c113ea4f5ceafd1e772ddcb0030a82e1d56c087b08" +dependencies = [ + "new_debug_unreachable", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec86235f5fcc2a73650310756d2ac5b138a5780bbbdfae3eeccec992c435ba4f" +dependencies = [ + "thiserror-impl 2.0.20", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc04cd3e1236dd4a98afca4569f2deb3f120e5422a4023be2cb683f8486292af" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "time" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb87b95ec50ddfa440816d227a17b2ccbdda963a316a727fda0fc4334f7d134" +dependencies = [ + "deranged", + "num-conv", + "powerfmt", + "serde_core", + "time-core", + "time-macros", +] + +[[package]] +name = "time-core" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1c906769ad99c88eaa54e728060edef082f8e358ff32030cb7c7d315e81109" + +[[package]] +name = "time-macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e689342a48d2ea927c87ea50cabf8594854bf940e9310208848d680d668ed85" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "tinystr" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1e27c91459209c2986af3dcf603a5a74a4368754ce37414f59acc971167f643" +dependencies = [ + "displaydoc", + "zerovec", +] + +[[package]] +name = "tinyvec" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd3ca314f692efd6c868f8408f53fe444634a845f96c028b97d35f6a1f79f0ee" + +[[package]] +name = "tokio" +version = "1.53.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "202caea871b69668250d242070849eb495be178ed697a3e98aebce5bc81a0bed" +dependencies = [ + "bytes", + "libc", + "mio", + "pin-project-lite", + "socket2", + "windows-sys 0.61.2", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0c85f2c3ef0b1cd58b36682f4b17aaa995f0e5db534d85692b4903abce21f67" +dependencies = [ + "rustls", + "tokio", +] + +[[package]] +name = "tokio-util" +version = "0.7.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "494815d09bf52b5548659851081238f0ca39ff638363907596da739561c62c52" +dependencies = [ + "bytes", + "futures-core", + "futures-sink", + "pin-project-lite", + "tokio", +] + +[[package]] +name = "toml" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "185d8ab0dfbb35cf1399a6344d8484209c088f75f8f68230da55d48d95d43e3d" +dependencies = [ + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "toml_edit 0.20.2", +] + +[[package]] +name = "toml" +version = "0.9.12+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf92845e79fc2e2def6a5d828f0801e29a2f8acc037becc5ab08595c7d5e9863" +dependencies = [ + "indexmap 2.14.2", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 0.7.5+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 0.7.15", +] + +[[package]] +name = "toml" +version = "1.1.6+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "920602543f0911ab71da12c50d59701da54c196d1a2bf5cb4b75667f137a406a" +dependencies = [ + "indexmap 2.14.2", + "serde_core", + "serde_spanned 1.1.1", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "toml_writer", + "winnow 1.0.4", +] + +[[package]] +name = "toml_datetime" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cda73e2f1397b1262d6dfdcef8aafae14d1de7748d66822d3bfeeb6d03e5e4b" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_datetime" +version = "0.7.5+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "92e1cfed4a3038bc5a127e35a2d360f145e1f4b971b551a2ba5fd7aedf7e1347" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.19.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "396e4d48bbb2b7554c944bde63101b5ae446cff6ec4a24227428f15eb72ef338" +dependencies = [ + "indexmap 2.14.2", + "serde", + "serde_spanned 0.6.9", + "toml_datetime 0.6.3", + "winnow 0.5.40", +] + +[[package]] +name = "toml_edit" +version = "0.25.15+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1340ea94a5856333492c9064b02c778b191dd2c853778d9609debdcdfea3a614" +dependencies = [ + "indexmap 2.14.2", + "toml_datetime 1.1.1+spec-1.1.0", + "toml_parser", + "winnow 1.0.4", +] + +[[package]] +name = "toml_parser" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56" +dependencies = [ + "winnow 1.0.4", +] + +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "pin-project-lite", + "tracing-core", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tray-icon" +version = "0.24.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "045979e3f037cd18ad1cb2a419dfda133c5c29c9f3453370079f2255d46c257e" +dependencies = [ + "crossbeam-channel", + "dirs", + "libappindicator", + "muda", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-foundation", + "once_cell", + "png 0.18.1", + "serde", + "thiserror 2.0.20", + "windows-sys 0.61.2", +] + +[[package]] +name = "try-lock" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" + +[[package]] +name = "typeid" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c" + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "unic-char-property" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8c57a407d9b6fa02b4795eb81c5b6652060a15a7903ea981f3d723e6c0be221" +dependencies = [ + "unic-char-range", +] + +[[package]] +name = "unic-char-range" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0398022d5f700414f6b899e10b8348231abf9173fa93144cbc1a43b9793c1fbc" + +[[package]] +name = "unic-common" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc" + +[[package]] +name = "unic-ucd-ident" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e230a37c0381caa9219d67cf063aa3a375ffed5bf541a452db16e744bdab6987" +dependencies = [ + "unic-char-property", + "unic-char-range", + "unic-ucd-version", +] + +[[package]] +name = "unic-ucd-version" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96bd2f2237fe450fcd0a1d2f5f4e91711124f7857ba2e964247776ebeeb7b0c4" +dependencies = [ + "unic-common", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", + "serde_derive", +] + +[[package]] +name = "urlpattern" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70acd30e3aa1450bc2eece896ce2ad0d178e9c079493819301573dae3c37ba6d" +dependencies = [ + "regex", + "serde", + "unic-ucd-ident", + "url", +] + +[[package]] +name = "utf8_iter" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" + +[[package]] +name = "uuid" +version = "1.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef6dac1e96601b4fb3acccccff2139741fcb757cb9a36089bf5be91cfb285ce" +dependencies = [ + "getrandom 0.4.3", + "js-sys", + "serde_core", + "wasm-bindgen", +] + +[[package]] +name = "version-compare" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03c2856837ef78f57382f06b2b8563a2f512f7185d732608fd9176cb3b8edf0e" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "vswhom" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be979b7f07507105799e854203b470ff7c78a1639e330a58f183b5fea574608b" +dependencies = [ + "libc", + "vswhom-sys", +] + +[[package]] +name = "vswhom-sys" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb067e4cbd1ff067d1df46c9194b5de0e98efd2810bbc95c5d5e5f25a3231150" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "want" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" +dependencies = [ + "try-lock", +] + +[[package]] +name = "wasi" +version = "0.11.1+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aecb87a33d3b0c5e3b7aa46336eaf486cffafbd281b195e4c8b80d50df2351bf" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.78" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef4c5d3d2cdf5c54f4231181768f5510842e350db025faf1f7163b1030ed928" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a690d511e3c1a8b3a55e33511e3c2c00c78415cd23650f32b808627f5696b9ed" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "411e4887f0071ef2d2164a9d5fdf2d20efbef78fccd3a78b0c10a1dc5295e48a" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 3.0.5", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.128" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81941cd78d0c92026c33e5e01312845a4cb1e9af3407f9134b100dd03144103e" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wasm-streams" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d1ec4f6517c9e11ae630e200b2b65d193279042e28edd4a2cda233e46670bbb" +dependencies = [ + "futures-util", + "js-sys", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + +[[package]] +name = "web-sys" +version = "0.3.105" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fbddc4a036f00ec4f18c83445bd3115cb306a91da554919a099d9222fe4a7f8" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web_atoms" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba8b815c1b593dc0baf78dd0f4fc8fdb2de53198fb1163738093e9a311c33fb3" +dependencies = [ + "phf", + "phf_codegen", + "string_cache", + "string_cache_codegen", +] + +[[package]] +name = "webkit2gtk" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1027150013530fb2eaf806408df88461ae4815a45c541c8975e61d6f2fc4793" +dependencies = [ + "bitflags 1.3.2", + "cairo-rs", + "gdk", + "gdk-sys", + "gio", + "gio-sys", + "glib", + "glib-sys", + "gobject-sys", + "gtk", + "gtk-sys", + "javascriptcore-rs", + "libc", + "once_cell", + "soup3", + "webkit2gtk-sys", +] + +[[package]] +name = "webkit2gtk-sys" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "916a5f65c2ef0dfe12fff695960a2ec3d4565359fdbb2e9943c974e06c734ea5" +dependencies = [ + "bitflags 1.3.2", + "cairo-sys-rs", + "gdk-sys", + "gio-sys", + "glib-sys", + "gobject-sys", + "gtk-sys", + "javascriptcore-rs-sys", + "libc", + "pkg-config", + "soup3-sys", + "system-deps", +] + +[[package]] +name = "webpki-roots" +version = "1.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dcd9d09a39985f5344844e66b0c530a33843579125f23e21e9f0f220850f22a" +dependencies = [ + "rustls-pki-types", +] + +[[package]] +name = "webview2-com" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7130243a7a5b33c54a444e54842e6a9e133de08b5ad7b5861cd8ed9a6a5bc96a" +dependencies = [ + "webview2-com-macros", + "webview2-com-sys", + "windows", + "windows-core 0.61.2", + "windows-implement", + "windows-interface", +] + +[[package]] +name = "webview2-com-macros" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67a921c1b6914c367b2b823cd4cde6f96beec77d30a939c8199bb377cf9b9b54" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "webview2-com-sys" +version = "0.38.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" +dependencies = [ + "thiserror 2.0.20", + "windows", + "windows-core 0.61.2", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "window-vibrancy" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9bec5a31f3f9362f2258fd0e9c9dd61a9ca432e7306cc78c444258f0dce9a9c" +dependencies = [ + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "raw-window-handle", + "windows-sys 0.59.0", + "windows-version", +] + +[[package]] +name = "windows" +version = "0.61.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9babd3a767a4c1aef6900409f85f5d53ce2544ccdfaa86dad48c91782c6d6893" +dependencies = [ + "windows-collections", + "windows-core 0.61.2", + "windows-future", + "windows-link 0.1.3", + "windows-numerics", +] + +[[package]] +name = "windows-collections" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3beeceb5e5cfd9eb1d76b381630e82c4241ccd0d27f1a39ed41b2760b255c5e8" +dependencies = [ + "windows-core 0.61.2", +] + +[[package]] +name = "windows-core" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.1.3", + "windows-result 0.3.4", + "windows-strings 0.4.2", +] + +[[package]] +name = "windows-core" +version = "0.62.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8e83a14d34d0623b51dce9581199302a221863196a1dde71a7663a4c2be9deb" +dependencies = [ + "windows-implement", + "windows-interface", + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + +[[package]] +name = "windows-future" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", + "windows-threading", +] + +[[package]] +name = "windows-implement" +version = "0.60.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "053e2e040ab57b9dc951b72c264860db7eb3b0200ba345b4e4c3b14f67855ddf" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-interface" +version = "0.59.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f316c4a2570ba26bbec722032c4099d8c8bc095efccdc15688708623367e358" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "windows-link" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-numerics" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" +dependencies = [ + "windows-core 0.61.2", + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-result" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7781fa89eaf60850ac3d2da7af8e5242a5ea78d1a11c49bf2910bb5a73853eb5" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-strings" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-strings" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7837d08f69c77cf6b07689544538e017c1bfcf57e34b4c0ff58e6c2cd3b37091" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows-threading" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" +dependencies = [ + "windows-link 0.1.3", +] + +[[package]] +name = "windows-version" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4060a1da109b9d0326b7262c8e12c84df67cc0dbc9e33cf49e01ccc2eb63631" +dependencies = [ + "windows-link 0.2.1", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + +[[package]] +name = "winnow" +version = "0.7.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df79d97927682d2fd8adb29682d1140b343be4ac0f08fd68b7765d9c059d3945" + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "winreg" +version = "0.55.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb5a765337c50e9ec252c2069be9bf91c7df47afb103b642ba3a53bf8101be97" +dependencies = [ + "cfg-if", + "windows-sys 0.59.0", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "writeable" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ad82d2a33cdc9674dc7465672f271e096168fcdbe0f799d9e6db8c5892679dc" + +[[package]] +name = "wry" +version = "0.55.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "186f9871daa55fd9c016578b810d149de58367113db7fb72b462d2323ce19514" +dependencies = [ + "base64 0.22.1", + "block2", + "cookie", + "crossbeam-channel", + "dirs", + "dom_query", + "dpi", + "dunce", + "gdkx11", + "gtk", + "http", + "javascriptcore-rs", + "jni", + "libc", + "ndk", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "objc2-web-kit", + "once_cell", + "percent-encoding", + "raw-window-handle", + "sha2", + "soup3", + "tao-macros", + "thiserror 2.0.20", + "url", + "webkit2gtk", + "webkit2gtk-sys", + "webview2-com", + "windows", + "windows-core 0.61.2", + "windows-version", + "x11-dl", +] + +[[package]] +name = "x11" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "502da5464ccd04011667b11c435cb992822c2c0dbde1770c988480d312a0db2e" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "yoke" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "709fe23a0424b6a435d82152b1bd3fdfb0833487d5fa90d05d42762a9891fef5" +dependencies = [ + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33811428bee40dbceb6d545e95754741d17a6aef9a4849f0fd62e2ba4f412a78" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", + "synstructure", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", + "synstructure", +] + +[[package]] +name = "zeroize" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e13c156562582aa81c60cb29407084cdb54c4164760106ab78e6c5b0858cf64e" + +[[package]] +name = "zerotrie" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ea269c3bd32f0a32c321907a2ae912ba6f4649bb0fc764a15627e99a7095a3f" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", +] + +[[package]] +name = "zerovec" +version = "0.11.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb0464e17806c1d976d5cba29399c7f08e516e279e2ba493f63123b5fca67dd8" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.11.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34df6fc39dbd26ddc9c10e6a2984476e13acce22e64e4487636ef494369225da" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.5", +] + +[[package]] +name = "zlib-rs" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b268e58e7c693d7c271f93ffc4ba3b380412554231c85bf61ca7af91042a4112" + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/apps/desktop/src-tauri/Cargo.toml b/apps/desktop/src-tauri/Cargo.toml new file mode 100644 index 0000000..d54d112 --- /dev/null +++ b/apps/desktop/src-tauri/Cargo.toml @@ -0,0 +1,21 @@ +[package] +name = "opsxdb-desktop" +version = "0.1.0" +description = "OpsxDB Tauri desktop shell" +authors = ["OpsxDB"] +edition = "2021" + +[lib] +name = "opsxdb_desktop_lib" +crate-type = ["staticlib", "cdylib", "rlib"] + +[build-dependencies] +tauri-build = { version = "2", features = [] } + +[dependencies] +tauri = { version = "2", features = [] } # path via Manager +serde = { version = "1", features = ["derive"] } +serde_json = "1" +reqwest = { version = "0.12", default-features = false, features = ["rustls-tls", "json", "blocking"] } +tokio = { version = "1", features = ["time"] } +url = "2" diff --git a/apps/desktop/src-tauri/binaries/README.md b/apps/desktop/src-tauri/binaries/README.md new file mode 100644 index 0000000..bc991c0 --- /dev/null +++ b/apps/desktop/src-tauri/binaries/README.md @@ -0,0 +1,8 @@ +# Sidecar binaries + +`./run.sh package` 会把 `opsxdb-server` 复制为带目标三元组后缀的文件,例如: + +- `opsxdb-server-aarch64-apple-darwin` +- `opsxdb-server-x86_64-apple-darwin` + +Tauri `externalBin` 在构建时按当前 triple 解析。 diff --git a/apps/desktop/src-tauri/build.rs b/apps/desktop/src-tauri/build.rs new file mode 100644 index 0000000..d860e1e --- /dev/null +++ b/apps/desktop/src-tauri/build.rs @@ -0,0 +1,3 @@ +fn main() { + tauri_build::build() +} diff --git a/apps/desktop/src-tauri/capabilities/default.json b/apps/desktop/src-tauri/capabilities/default.json new file mode 100644 index 0000000..a1ee68a --- /dev/null +++ b/apps/desktop/src-tauri/capabilities/default.json @@ -0,0 +1,11 @@ +{ + "$schema": "../gen/schemas/desktop-schema.json", + "identifier": "default", + "description": "Default capabilities for the OpsxDB main window", + "windows": [ + "main" + ], + "permissions": [ + "core:default" + ] +} diff --git a/apps/desktop/src-tauri/icons/128x128.png b/apps/desktop/src-tauri/icons/128x128.png new file mode 100644 index 0000000..dd5d5a5 Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128.png differ diff --git a/apps/desktop/src-tauri/icons/128x128@2x.png b/apps/desktop/src-tauri/icons/128x128@2x.png new file mode 100644 index 0000000..3d58e3e Binary files /dev/null and b/apps/desktop/src-tauri/icons/128x128@2x.png differ diff --git a/apps/desktop/src-tauri/icons/32x32.png b/apps/desktop/src-tauri/icons/32x32.png new file mode 100644 index 0000000..c400508 Binary files /dev/null and b/apps/desktop/src-tauri/icons/32x32.png differ diff --git a/apps/desktop/src-tauri/icons/64x64.png b/apps/desktop/src-tauri/icons/64x64.png new file mode 100644 index 0000000..518db93 Binary files /dev/null and b/apps/desktop/src-tauri/icons/64x64.png differ diff --git a/apps/desktop/src-tauri/icons/Square107x107Logo.png b/apps/desktop/src-tauri/icons/Square107x107Logo.png new file mode 100644 index 0000000..ce0c62a Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square107x107Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square142x142Logo.png b/apps/desktop/src-tauri/icons/Square142x142Logo.png new file mode 100644 index 0000000..d3209c2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square142x142Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square150x150Logo.png b/apps/desktop/src-tauri/icons/Square150x150Logo.png new file mode 100644 index 0000000..dbb1c82 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square150x150Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square284x284Logo.png b/apps/desktop/src-tauri/icons/Square284x284Logo.png new file mode 100644 index 0000000..8078d1d Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square284x284Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square30x30Logo.png b/apps/desktop/src-tauri/icons/Square30x30Logo.png new file mode 100644 index 0000000..0a73e00 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square30x30Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square310x310Logo.png b/apps/desktop/src-tauri/icons/Square310x310Logo.png new file mode 100644 index 0000000..1b1b8c2 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square310x310Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square44x44Logo.png b/apps/desktop/src-tauri/icons/Square44x44Logo.png new file mode 100644 index 0000000..2cd5e2b Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square44x44Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square71x71Logo.png b/apps/desktop/src-tauri/icons/Square71x71Logo.png new file mode 100644 index 0000000..01015a9 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square71x71Logo.png differ diff --git a/apps/desktop/src-tauri/icons/Square89x89Logo.png b/apps/desktop/src-tauri/icons/Square89x89Logo.png new file mode 100644 index 0000000..a8250c6 Binary files /dev/null and b/apps/desktop/src-tauri/icons/Square89x89Logo.png differ diff --git a/apps/desktop/src-tauri/icons/StoreLogo.png b/apps/desktop/src-tauri/icons/StoreLogo.png new file mode 100644 index 0000000..70abd6b Binary files /dev/null and b/apps/desktop/src-tauri/icons/StoreLogo.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml b/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..2ffbf24 --- /dev/null +++ b/apps/desktop/src-tauri/icons/android/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..ec23ae9 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..7afb752 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..38a17e0 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-hdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..2fb0fe7 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..9fcd5d6 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..e37aa7f Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-mdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..1d93418 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..23d33e0 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..bdf6a4b Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..28c4417 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..543a3c0 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..70f66f0 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2404cb4 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png new file mode 100644 index 0000000..63134f3 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_foreground.png differ diff --git a/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..f4fdb65 Binary files /dev/null and b/apps/desktop/src-tauri/icons/android/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml b/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml new file mode 100644 index 0000000..ea9c223 --- /dev/null +++ b/apps/desktop/src-tauri/icons/android/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #fff + \ No newline at end of file diff --git a/apps/desktop/src-tauri/icons/icon.icns b/apps/desktop/src-tauri/icons/icon.icns new file mode 100644 index 0000000..73fcf19 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.icns differ diff --git a/apps/desktop/src-tauri/icons/icon.ico b/apps/desktop/src-tauri/icons/icon.ico new file mode 100644 index 0000000..240a49e Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.ico differ diff --git a/apps/desktop/src-tauri/icons/icon.png b/apps/desktop/src-tauri/icons/icon.png new file mode 100644 index 0000000..03b7766 Binary files /dev/null and b/apps/desktop/src-tauri/icons/icon.png differ diff --git a/apps/desktop/src-tauri/icons/icon.svg b/apps/desktop/src-tauri/icons/icon.svg new file mode 100644 index 0000000..8accdbc --- /dev/null +++ b/apps/desktop/src-tauri/icons/icon.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png new file mode 100644 index 0000000..b32b5ce Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png new file mode 100644 index 0000000..ceeee7f Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png new file mode 100644 index 0000000..ceeee7f Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png new file mode 100644 index 0000000..b64c721 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-20x20@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png new file mode 100644 index 0000000..dded63b Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png new file mode 100644 index 0000000..d3b6745 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png new file mode 100644 index 0000000..d3b6745 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png new file mode 100644 index 0000000..f4e1b0a Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-29x29@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png new file mode 100644 index 0000000..ceeee7f Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png new file mode 100644 index 0000000..9dc1641 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x-1.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png new file mode 100644 index 0000000..9dc1641 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png new file mode 100644 index 0000000..1851a00 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-40x40@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png new file mode 100644 index 0000000..ae96588 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-512@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png new file mode 100644 index 0000000..1851a00 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png new file mode 100644 index 0000000..c8ee8b4 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-60x60@3x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png new file mode 100644 index 0000000..7351eb1 Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@1x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png new file mode 100644 index 0000000..1be910b Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-76x76@2x.png differ diff --git a/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png b/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png new file mode 100644 index 0000000..bf53b2e Binary files /dev/null and b/apps/desktop/src-tauri/icons/ios/AppIcon-83.5x83.5@2x.png differ diff --git a/apps/desktop/src-tauri/src/lib.rs b/apps/desktop/src-tauri/src/lib.rs new file mode 100644 index 0000000..b3d004e --- /dev/null +++ b/apps/desktop/src-tauri/src/lib.rs @@ -0,0 +1,150 @@ +mod sidecar; + +use tauri::Manager; + +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; +use std::time::{Duration, Instant}; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct BodySpec { + mode: String, + raw: Option, +} + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +struct HttpSpec { + method: String, + url: String, + #[serde(default)] + headers: HashMap, + query: Option>, + body: Option, + timeout_ms: Option, +} + +#[derive(Debug, Serialize)] +#[serde(rename_all = "camelCase")] +struct HttpResult { + status: u16, + headers: HashMap, + body: String, + time_ms: u128, + size: usize, +} + +#[derive(Debug, Deserialize)] +struct WsSpec { + url: String, + protocols: Option>, + headers: Option>, +} + +fn validate_http_url(raw: &str) -> Result { + let parsed = url::Url::parse(raw).map_err(|e| format!("invalid URL: {e}"))?; + match parsed.scheme() { + "http" | "https" => {} + _ => return Err("only http/https schemes are allowed".into()), + } + match parsed.host_str().unwrap_or("").to_ascii_lowercase().as_str() { + "169.254.169.254" | "metadata.google.internal" => { + return Err("blocked metadata host".into()) + } + _ => {} + } + Ok(parsed) +} + +#[tauri::command] +async fn http_send(spec: HttpSpec) -> Result { + let mut url = validate_http_url(&spec.url)?; + if let Some(query) = &spec.query { + let mut pairs = url.query_pairs_mut(); + for (key, value) in query { + pairs.append_pair(key, value); + } + } + + let method = reqwest::Method::from_bytes(spec.method.as_bytes()) + .map_err(|e| format!("invalid method: {e}"))?; + let timeout = Duration::from_millis(spec.timeout_ms.unwrap_or(30_000)); + let client = reqwest::Client::builder() + .timeout(timeout) + .build() + .map_err(|e| e.to_string())?; + let mut request = client.request(method, url); + for (key, value) in spec.headers { + request = request.header(&key, &value); + } + if let Some(body) = spec.body { + if body.mode != "none" { + request = request.body(body.raw.unwrap_or_default()); + } + } + + let started = Instant::now(); + let response = request.send().await.map_err(|e| e.to_string())?; + let status = response.status().as_u16(); + let headers = response + .headers() + .iter() + .map(|(k, v)| (k.to_string(), v.to_str().unwrap_or("").to_string())) + .collect(); + let bytes = response.bytes().await.map_err(|e| e.to_string())?; + let size = bytes.len(); + let body = String::from_utf8_lossy(&bytes).into_owned(); + Ok(HttpResult { + status, + headers, + body, + time_ms: started.elapsed().as_millis(), + size, + }) +} + +#[tauri::command] +async fn ws_connect(spec: WsSpec) -> Result { + let _ = (&spec.protocols, &spec.headers); + // TODO(week2): hold WebSocket sessions in managed state and emit messages. + Err(format!( + "WebSocket native tunnel is not implemented yet for {} (use browser WS in MVP)", + spec.url + )) +} + +#[tauri::command] +async fn ws_send(session_id: String, data: String) -> Result<(), String> { + let _ = (session_id, data); + Err("WebSocket native send is a Week 2 stub".into()) +} + +#[tauri::command] +async fn ws_close(session_id: String) -> Result<(), String> { + let _ = session_id; + Ok(()) +} + +#[cfg_attr(mobile, tauri::mobile_entry_point)] +pub fn run() { + tauri::Builder::default() + .manage(sidecar::SidecarState::default()) + .setup(|app| { + let state = app.state::(); + sidecar::ensure_backend(app.handle(), &state); + Ok(()) + }) + .invoke_handler(tauri::generate_handler![ + http_send, + ws_connect, + ws_send, + ws_close + ]) + .build(tauri::generate_context!()) + .expect("error while building OpsxDB desktop") + .run(|app, event| { + let state = app.state::(); + sidecar::on_run_event(app, &event, &state); + }); +} diff --git a/apps/desktop/src-tauri/src/main.rs b/apps/desktop/src-tauri/src/main.rs new file mode 100644 index 0000000..a5f036e --- /dev/null +++ b/apps/desktop/src-tauri/src/main.rs @@ -0,0 +1,3 @@ +fn main() { + opsxdb_desktop_lib::run(); +} diff --git a/apps/desktop/src-tauri/src/sidecar.rs b/apps/desktop/src-tauri/src/sidecar.rs new file mode 100644 index 0000000..2d182a8 --- /dev/null +++ b/apps/desktop/src-tauri/src/sidecar.rs @@ -0,0 +1,108 @@ +//! 桌面启动时拉起本机 OpsxDB Gin 后端(默认 127.0.0.1:8788)。 +use std::path::PathBuf; +use std::process::{Child, Command, Stdio}; +use std::sync::Mutex; +use std::thread; +use std::time::Duration; +use tauri::{AppHandle, Manager, RunEvent}; + +const HEALTH: &str = "http://127.0.0.1:8788/health"; +const ADDR: &str = "127.0.0.1:8788"; + +pub struct SidecarState { + child: Mutex>, +} + +impl Default for SidecarState { + fn default() -> Self { + Self { + child: Mutex::new(None), + } + } +} + +fn health_ok() -> bool { + ureq_get(HEALTH).is_ok() +} + +/// 轻量 GET,避免再引 reqwest 阻塞;用 std 即可。 +fn ureq_get(url: &str) -> Result<(), ()> { + let Ok(resp) = reqwest::blocking::Client::builder() + .timeout(Duration::from_secs(1)) + .build() + .and_then(|c| c.get(url).send()) + else { + return Err(()); + }; + if resp.status().is_success() { + Ok(()) + } else { + Err(()) + } +} + +fn candidate_bins(app: &AppHandle) -> Vec { + let mut out = Vec::new(); + // 打包资源:externalBin → 与可执行文件同级的 binaries 或 resource + if let Ok(dir) = app.path().resource_dir() { + out.push(dir.join("opsxdb-server")); + out.push(dir.join("binaries").join("opsxdb-server")); + } + if let Ok(exe) = app.path().executable_dir() { + out.push(exe.join("opsxdb-server")); + out.push(exe.join("binaries").join("opsxdb-server")); + } + // 开发态:仓库 apps/server/bin + out.push(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../server/bin/opsxdb-server")); + out.push(PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../../../apps/server/bin/opsxdb-server")); + out +} + +fn spawn_server(bin: &PathBuf) -> std::io::Result { + Command::new(bin) + .env("OPSXDB_ADDR", ADDR) + .env("GIN_MODE", "release") + .stdout(Stdio::null()) + .stderr(Stdio::null()) + .spawn() +} + +pub fn ensure_backend(app: &AppHandle, state: &SidecarState) { + if health_ok() { + return; + } + for bin in candidate_bins(app) { + if !bin.exists() { + continue; + } + match spawn_server(&bin) { + Ok(child) => { + *state.child.lock().unwrap() = Some(child); + for _ in 0..30 { + if health_ok() { + return; + } + thread::sleep(Duration::from_millis(100)); + } + return; + } + Err(_) => continue, + } + } +} + +pub fn stop_backend(state: &SidecarState) { + if let Ok(mut g) = state.child.lock() { + if let Some(mut child) = g.take() { + let _ = child.kill(); + let _ = child.wait(); + } + } +} + +pub fn on_run_event(app: &AppHandle, event: &RunEvent, state: &SidecarState) { + if matches!(event, RunEvent::Exit | RunEvent::ExitRequested { .. }) { + stop_backend(state); + let _ = app; + } +} diff --git a/apps/desktop/src-tauri/tauri.conf.json b/apps/desktop/src-tauri/tauri.conf.json new file mode 100644 index 0000000..f870da9 --- /dev/null +++ b/apps/desktop/src-tauri/tauri.conf.json @@ -0,0 +1,43 @@ +{ + "$schema": "https://schema.tauri.app/config/2", + "productName": "OpsxDB", + "version": "0.1.0", + "identifier": "com.opsx.db", + "build": { + "beforeDevCommand": "bash -lc 'curl -fsS http://127.0.0.1:5174 >/dev/null 2>&1 || npm --prefix ../.. run dev:web'", + "devUrl": "http://127.0.0.1:5174", + "beforeBuildCommand": "bash -lc 'cd ../.. && VITE_OPSXDB_API=http://127.0.0.1:8788 npm run build:web'", + "frontendDist": "../../web/dist" + }, + "app": { + "windows": [ + { + "title": "OpsxDB", + "width": 1280, + "height": 800, + "minWidth": 960, + "minHeight": 640, + "resizable": true, + "fullscreen": false, + "label": "main" + } + ], + "security": { + "csp": null + } + }, + "bundle": { + "active": true, + "targets": "all", + "icon": [ + "icons/32x32.png", + "icons/128x128.png", + "icons/128x128@2x.png", + "icons/icon.icns", + "icons/icon.ico" + ], + "externalBin": [ + "binaries/opsxdb-server" + ] + } +} diff --git a/apps/server/cmd/opsxdb/main.go b/apps/server/cmd/opsxdb/main.go new file mode 100644 index 0000000..4b2c887 --- /dev/null +++ b/apps/server/cmd/opsxdb/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "log" + "os" + "strings" + + "opsxdb/server/internal/server" +) + +func main() { + addr := resolveListenAddr() + log.Printf("OpsxDB server listening on %s", addr) + if err := server.NewRouter().Run(addr); err != nil { + log.Fatal(err) + } +} + +// resolveListenAddr:默认仅本机 127.0.0.1:8788(桌面 sidecar 同机访问)。 +// 显式对外:OPSXDB_BIND_PUBLIC=1 或 OPSXDB_ADDR=:8788 / 0.0.0.0:8788。 +func resolveListenAddr() string { + if v := strings.TrimSpace(os.Getenv("OPSXDB_ADDR")); v != "" { + return v + } + pub := strings.ToLower(strings.TrimSpace(os.Getenv("OPSXDB_BIND_PUBLIC"))) + if pub == "1" || pub == "true" || pub == "yes" { + return ":8788" + } + return "127.0.0.1:8788" +} diff --git a/apps/server/config.example.yaml b/apps/server/config.example.yaml new file mode 100644 index 0000000..be69e9f --- /dev/null +++ b/apps/server/config.example.yaml @@ -0,0 +1,7 @@ +server: + addr: ":8788" +proxy: + timeout_ms: 30000 +# Environment overrides: +# OPSXDB_ADDR=:8788 +# OPSXDB_PROXY_TIMEOUT_MS=30000 diff --git a/apps/server/go.mod b/apps/server/go.mod new file mode 100644 index 0000000..7c13c1d --- /dev/null +++ b/apps/server/go.mod @@ -0,0 +1,63 @@ +module opsxdb/server + +go 1.25.0 + +require ( + github.com/gin-gonic/gin v1.10.0 + github.com/go-sql-driver/mysql v1.8.1 + github.com/jackc/pgx/v5 v5.7.2 + github.com/microsoft/go-mssqldb v1.8.0 + github.com/redis/go-redis/v9 v9.7.0 + github.com/sijms/go-ora/v2 v2.8.22 + golang.org/x/crypto v0.48.0 + modernc.org/sqlite v1.34.5 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/alicebob/miniredis/v2 v2.39.0 // indirect + github.com/bytedance/gopkg v0.1.3 // indirect + github.com/bytedance/sonic v1.15.0 // indirect + github.com/bytedance/sonic/loader v0.5.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/cloudwego/base64x v0.1.6 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.12 // indirect + github.com/gin-contrib/sse v1.1.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.30.1 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.3.0 // indirect + github.com/kr/pretty v0.3.1 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rogpeppe/go-internal v1.10.0 // indirect + github.com/stretchr/testify v1.11.1 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.3.1 // indirect + github.com/yuin/gopher-lua v1.1.1 // indirect + golang.org/x/arch v0.22.0 // indirect + golang.org/x/net v0.51.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.41.0 // indirect + golang.org/x/text v0.34.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.55.3 // indirect + modernc.org/mathutil v1.6.0 // indirect + modernc.org/memory v1.8.0 // indirect +) diff --git a/apps/server/go.sum b/apps/server/go.sum new file mode 100644 index 0000000..c02743e --- /dev/null +++ b/apps/server/go.sum @@ -0,0 +1,185 @@ +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1 h1:E+OJmp2tPvt1W+amx48v1eqbjDYsgN+RzP4q16yV5eM= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.11.1/go.mod h1:a6xsAQUZg+VsS3TJ05SRp524Hs4pZ/AeFSr5ENf0Yjo= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0 h1:U2rTu3Ef+7w9FHKIAXM6ZyqF3UOWJZ12zIm8zECAFfg= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.6.0/go.mod h1:9kIvujWAA58nmPmWB1m23fyWic1kYZMxD9CxaWn4Qpg= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0 h1:jBQA3cKT4L2rWMpgE7Yt3Hwh2aUj8KXjIGLxjHeYNNo= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.8.0/go.mod h1:4OG6tQ9EOP/MT0NMjDlRzWoVFxfu9rN9B2X+tlSVktg= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1 h1:MyVTgWR8qd/Jw1Le0NZebGBUCLbtak3bJ3z1OlqZBpw= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.0.1/go.mod h1:GpPjLhVR9dnUoJMyHWSPy71xY9/lcmpzIPZXmF0FCVY= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0 h1:D3occbWoio4EBLkbkevetNMAVX197GkzbUMtqjGWn80= +github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/internal v1.0.0/go.mod h1:bTSOgj05NGRuHHhQwAdPnYr9TOdNmKlZTgGLL6nyAdI= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2 h1:XHOnouVk1mxXfQidrMEnLlPk9UMeRtyBTnEFtxkV0kU= +github.com/AzureAD/microsoft-authentication-library-for-go v1.2.2/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI= +github.com/alicebob/miniredis/v2 v2.39.0 h1:M7WbmV5BmV56L8KTG0rw6vEQ+woTOghpDgin2xv4A0g= +github.com/alicebob/miniredis/v2 v2.39.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM= +github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs= +github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c= +github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA= +github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0= +github.com/bytedance/gopkg v0.1.3 h1:TPBSwH8RsouGCBcMBktLt1AymVo2TVsBVCY4b6TnZ/M= +github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= +github.com/bytedance/sonic v1.15.0 h1:/PXeWFaR5ElNcVE84U0dOHjiMHQOwNIx3K4ymzh/uSE= +github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic/loader v0.5.0 h1:gXH3KVnatgY7loH5/TkeVyXPfESoqSBSBEiDd5VjlgE= +github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI2M= +github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/gabriel-vasile/mimetype v1.4.12 h1:e9hWvmLYvtp846tLHam2o++qitpguFiYCKbn0w9jyqw= +github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= +github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w= +github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w= +github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/golang-jwt/jwt/v5 v5.2.1 h1:OuVbFODueb089Lh128TAcimifWaLhJwVflnrgM17wHk= +github.com/golang-jwt/jwt/v5 v5.2.1/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd h1:gbpYu9NMq8jhDVbvlGkMFWCjLFlqqEZjEmObmhUy6Vo= +github.com/google/pprof v0.0.0-20240409012703-83162a5b38cd/go.mod h1:kf6iHlnVGwgKolg33glAes7Yg/8iWP8ukqeldJSO7jw= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/klauspost/cpuid/v2 v2.3.0 h1:S4CRMLnYUhGeDFDqkGriYKdfoFlDnMtqTiI/sFzhA9Y= +github.com/klauspost/cpuid/v2 v2.3.0/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/microsoft/go-mssqldb v1.8.0 h1:7cyZ/AT7ycDsEoWPIXibd+aVKFtteUNhDGf3aobP+tw= +github.com/microsoft/go-mssqldb v1.8.0/go.mod h1:6znkekS3T2vp0waiMhen4GPU1BiAsrP+iXHcE7a7rFo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= +github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/redis/go-redis/v9 v9.7.0 h1:HhLSs+B6O021gwzl+locl0zEDnyNkxMtf/Z3NNBMa9E= +github.com/redis/go-redis/v9 v9.7.0/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ= +github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog= +github.com/sijms/go-ora/v2 v2.8.22 h1:3ABgRzVKxS439cEgSLjFKutIwOyhnyi4oOSBywEdOlU= +github.com/sijms/go-ora/v2 v2.8.22/go.mod h1:QgFInVi3ZWyqAiJwzBQA+nbKYKH77tdp1PYoCqhR2dU= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.3.1 h1:waO7eEiFDwidsBN6agj1vJQ4AG7lh2yqXyOXqhgQuyY= +github.com/ugorji/go/codec v1.3.1/go.mod h1:pRBVtBSKl77K30Bv8R2P+cLSGaTtex6fsA2Wjqmfxj4= +github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= +github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= +golang.org/x/arch v0.22.0 h1:c/Zle32i5ttqRXjdLyyHZESLD/bB90DCU1g9l/0YBDI= +golang.org/x/arch v0.22.0/go.mod h1:dNHoOeKiyja7GTvF9NJS1l3Z2yntpQNzgrjh1cU103A= +golang.org/x/crypto v0.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= +golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.51.0 h1:94R/GTO7mt3/4wIKpcR5gkGmRLOuE/2hNGeWq/GBIFo= +golang.org/x/net v0.51.0/go.mod h1:aamm+2QF5ogm02fjy5Bb7CQ0WMt1/WVM7FtyaTLlA9Y= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= +golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= +golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= +golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk= +golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +modernc.org/cc/v4 v4.21.4 h1:3Be/Rdo1fpr8GrQ7IVw9OHtplU4gWbb+wNgeoBMmGLQ= +modernc.org/cc/v4 v4.21.4/go.mod h1:HM7VJTZbUCR3rV8EYBi9wxnJ0ZBRiGE5OeGXNA0IsLQ= +modernc.org/ccgo/v4 v4.19.2 h1:lwQZgvboKD0jBwdaeVCTouxhxAyN6iawF3STraAal8Y= +modernc.org/ccgo/v4 v4.19.2/go.mod h1:ysS3mxiMV38XGRTTcgo0DQTeTmAO4oCmJl1nX9VFI3s= +modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= +modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= +modernc.org/gc/v2 v2.4.1 h1:9cNzOqPyMJBvrUipmynX0ZohMhcxPtMccYgGOJdOiBw= +modernc.org/gc/v2 v2.4.1/go.mod h1:wzN5dK1AzVGoH6XOzc3YZ+ey/jPgYHLuVckd62P0GYU= +modernc.org/libc v1.55.3 h1:AzcW1mhlPNrRtjS5sS+eW2ISCgSOLLNyFzRh/V3Qj/U= +modernc.org/libc v1.55.3/go.mod h1:qFXepLhz+JjFThQ4kzwzOjA/y/artDeg+pcYnY+Q83w= +modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4= +modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo= +modernc.org/memory v1.8.0 h1:IqGTL6eFMaDZZhEWwcREgeMXYwmW83LYW8cROZYkg+E= +modernc.org/memory v1.8.0/go.mod h1:XPZ936zp5OMKGWPqbD3JShgd/ZoQ7899TUuQqxY+peU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sortutil v1.2.0 h1:jQiD3PfS2REGJNzNCMMaLSp/wdMNieTbKX920Cqdgqc= +modernc.org/sortutil v1.2.0/go.mod h1:TKU2s7kJMf1AE84OoiGppNHJwvB753OYfNl2WRb++Ss= +modernc.org/sqlite v1.34.5 h1:Bb6SR13/fjp15jt70CL4f18JIN7p7dnMExd+UFnF15g= +modernc.org/sqlite v1.34.5/go.mod h1:YLuNmX9NKs8wRNK2ko1LW1NGYcc9FkBO69JOt1AR9JE= +modernc.org/strutil v1.2.0 h1:agBi9dp1I+eOnxXeiZawM8F4LawKv4NzGWSaLfyeNZA= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= diff --git a/apps/server/internal/ai/stub.go b/apps/server/internal/ai/stub.go new file mode 100644 index 0000000..53eee33 --- /dev/null +++ b/apps/server/internal/ai/stub.go @@ -0,0 +1,97 @@ +package ai + +import ( + "fmt" + "strings" +) + +type ChatReq struct { + Action string `json:"action"` + Prompt string `json:"prompt"` + SQL string `json:"sql"` + Driver string `json:"driver"` + Database string `json:"database"` + Error string `json:"error"` +} + +type ChatRes struct { + Content string `json:"content"` + SQL string `json:"sql,omitempty"` +} + +func Chat(req ChatReq) ChatRes { + driver := req.Driver + if driver == "" { + driver = "mysql" + } + db := req.Database + if db == "" { + db = "your_db" + } + sql := strings.TrimSpace(req.SQL) + prompt := strings.TrimSpace(req.Prompt) + + switch strings.ToLower(req.Action) { + case "generate", "ask": + q := prompt + if q == "" { + q = "示例查询" + } + gen := fmt.Sprintf("-- 根据描述生成(%s / %s)\n-- 需求: %s\nSELECT *\nFROM `%s`.some_table\nWHERE 1=1\nLIMIT 100;", driver, db, q, db) + return ChatRes{ + Content: fmt.Sprintf("已根据「%s」生成 SQL(stub,可替换为真实模型)。\n\n```sql\n%s\n```", q, gen), + SQL: gen, + } + case "explain": + if sql == "" { + sql = "SELECT 1" + } + return ChatRes{Content: fmt.Sprintf("## 解释 SQL\n\n```sql\n%s\n```\n\n- 这是一条在 **%s** 上执行的查询。\n- 建议检查 WHERE 条件是否走索引,避免 `SELECT *` 拉取过大结果集。\n- (stub 回复)", sql, driver)} + case "optimize": + opt := sql + if opt == "" { + opt = "SELECT id, name FROM users WHERE status = 1 LIMIT 100;" + } else if strings.Contains(strings.ToUpper(opt), "SELECT *") { + opt = strings.Replace(opt, "*", "id /* TODO: 显式列 */", 1) + } + opt = strings.TrimSpace(opt) + if !strings.Contains(strings.ToUpper(opt), "LIMIT") { + opt = opt + "\nLIMIT 100;" + } + return ChatRes{ + Content: fmt.Sprintf("## 优化建议\n\n1. 避免不必要的全表扫描\n2. 显式列出列名\n3. 加合理 LIMIT\n\n```sql\n%s\n```", opt), + SQL: opt, + } + case "fix": + fixed := sql + if fixed == "" { + fixed = "SELECT 1 AS ok;" + } + msg := req.Error + if msg == "" { + msg = "未知错误" + } + return ChatRes{ + Content: fmt.Sprintf("## 修复错误\n\n原错误:`%s`\n\n尝试修正后的 SQL:\n\n```sql\n%s\n```\n\n(stub:请结合真实报错继续调整)", msg, fixed), + SQL: fixed, + } + case "dialect": + out := sql + if out == "" { + out = "SELECT * FROM dual" + } + out = strings.ReplaceAll(out, "`", "\"") + return ChatRes{ + Content: fmt.Sprintf("## 方言转换 → PostgreSQL 风格(示例)\n\n```sql\n%s\n```", out), + SQL: out, + } + case "sample": + sample := fmt.Sprintf("SELECT id, name, created_at\nFROM `%s`.users\nORDER BY id DESC\nLIMIT 20;", db) + return ChatRes{ + Content: fmt.Sprintf("## 样例 SQL(%s)\n\n```sql\n%s\n```", driver, sample), + SQL: sample, + } + default: + return ChatRes{Content: "未知 AI 动作。可用:ask / generate / explain / optimize / fix / dialect / sample"} + } +} diff --git a/apps/server/internal/db/danger.go b/apps/server/internal/db/danger.go new file mode 100644 index 0000000..ce6cf0c --- /dev/null +++ b/apps/server/internal/db/danger.go @@ -0,0 +1,50 @@ +package db + +import "strings" + +// IsDangerousSQL 识别可能破坏数据的语句,供前端二次确认。 +func IsDangerousSQL(sql string) bool { + s := stripSQLLead(sql) + if s == "" { + return false + } + up := strings.ToUpper(s) + for _, p := range []string{"DROP ", "DROP\t", "TRUNCATE ", "ALTER ", "GRANT ", "REVOKE "} { + if strings.HasPrefix(up, strings.TrimSpace(p)) || strings.HasPrefix(up, p) { + return true + } + } + if strings.HasPrefix(up, "DELETE") { + return !containsWhere(up) + } + if strings.HasPrefix(up, "UPDATE") { + return !containsWhere(up) + } + return false +} + +func containsWhere(up string) bool { + return strings.Contains(up, " WHERE ") || strings.Contains(up, "\nWHERE ") || strings.Contains(up, "\tWHERE ") +} + +func stripSQLLead(sql string) string { + s := strings.TrimSpace(sql) + for { + if strings.HasPrefix(s, "--") { + if i := strings.IndexByte(s, '\n'); i >= 0 { + s = strings.TrimSpace(s[i+1:]) + continue + } + return "" + } + if strings.HasPrefix(s, "/*") { + if i := strings.Index(s, "*/"); i >= 0 { + s = strings.TrimSpace(s[i+2:]) + continue + } + return "" + } + break + } + return s +} diff --git a/apps/server/internal/db/ident.go b/apps/server/internal/db/ident.go new file mode 100644 index 0000000..683a3d3 --- /dev/null +++ b/apps/server/internal/db/ident.go @@ -0,0 +1,19 @@ +package db + +import "strings" + +func quoteMySQLIdent(s string) string { + return "`" + strings.ReplaceAll(s, "`", "") + "`" +} + +func quotePGIdent(s string) string { + return `"` + strings.ReplaceAll(s, `"`, `""`) + `"` +} + +func splitSchemaTable(name string) (schema, table string) { + name = strings.TrimSpace(name) + if i := strings.LastIndex(name, "."); i > 0 { + return name[:i], name[i+1:] + } + return "", name +} diff --git a/apps/server/internal/db/mssql.go b/apps/server/internal/db/mssql.go new file mode 100644 index 0000000..e7400a3 --- /dev/null +++ b/apps/server/internal/db/mssql.go @@ -0,0 +1,139 @@ +package db + +import ( + "database/sql" + "fmt" + "net/url" + "strings" + + _ "github.com/microsoft/go-mssqldb" +) + +func openMSSQL(c ConnReq) (*sql.DB, error) { + host := c.Host + if host == "" { + host = "127.0.0.1" + } + port := c.Port + if port == 0 { + port = 1433 + } + user := c.User + if user == "" { + user = "sa" + } + dbName := c.Database + if dbName == "" { + dbName = "master" + } + mode := EffectiveSSLMode(c) + q := url.Values{} + q.Set("database", dbName) + q.Set("connection timeout", "5") + switch mode { + case "disable": + q.Set("encrypt", "disable") + case "skip-verify": + q.Set("encrypt", "true") + q.Set("TrustServerCertificate", "true") + if ca := strings.TrimSpace(c.SSLCA); ca != "" { + q.Set("certificate", ca) + } + case "require", "verify-ca": + q.Set("encrypt", "true") + q.Set("TrustServerCertificate", "false") + ca := strings.TrimSpace(c.SSLCA) + if mode == "verify-ca" && ca == "" { + return nil, fmt.Errorf("verify-ca 模式需要填写 CA 证书路径") + } + if ca != "" { + // go-mssqldb msdsn certificate= builds RootCAs from PEM/DER + q.Set("certificate", ca) + } + if host != "" { + q.Set("hostnameincertificate", host) + } + default: + return nil, fmt.Errorf("未知 SSL 模式: %s", mode) + } + dsn := fmt.Sprintf("sqlserver://%s:%s@%s:%d?%s", + url.QueryEscape(user), url.QueryEscape(c.Password), host, port, q.Encode()) + return sql.Open("sqlserver", dsn) +} + +func listDatabasesMSSQL(db *sql.DB) ([]string, error) { + rows, err := db.Query(`SELECT name FROM sys.databases WHERE name NOT IN ('master','tempdb','model','msdb') ORDER BY name`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listTablesMSSQL(db *sql.DB, database string) ([]string, error) { + _ = database + rows, err := db.Query(` + SELECT TABLE_SCHEMA + '.' + TABLE_NAME + FROM INFORMATION_SCHEMA.TABLES + WHERE TABLE_TYPE = 'BASE TABLE' + ORDER BY 1`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listColumnsMSSQL(dbh *sql.DB, _, table string) ([]ColumnInfo, error) { + schema, t := splitSchemaTable(table) + if schema == "" { + schema = "dbo" + } + rows, err := dbh.Query(` + SELECT COLUMN_NAME, DATA_TYPE, + ISNULL(CAST(CHARACTER_MAXIMUM_LENGTH AS varchar), ''), + IS_NULLABLE, COLUMN_DEFAULT + FROM INFORMATION_SCHEMA.COLUMNS + WHERE TABLE_SCHEMA = @p1 AND TABLE_NAME = @p2 + ORDER BY ORDINAL_POSITION`, schema, t) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ColumnInfo + for rows.Next() { + var name, typ, extra, nullable string + var def sql.NullString + if err := rows.Scan(&name, &typ, &extra, &nullable, &def); err != nil { + return nil, err + } + if extra != "" && extra != "-1" { + typ = typ + "(" + extra + ")" + } + col := ColumnInfo{Name: name, Type: typ, Nullable: nullable == "YES"} + if def.Valid { + col.Default = def.String + } + out = append(out, col) + } + if out == nil { + out = []ColumnInfo{} + } + return out, rows.Err() +} diff --git a/apps/server/internal/db/mysql.go b/apps/server/internal/db/mysql.go new file mode 100644 index 0000000..1be4788 --- /dev/null +++ b/apps/server/internal/db/mysql.go @@ -0,0 +1,152 @@ +package db + +import ( + "crypto/sha256" + "database/sql" + "encoding/hex" + "fmt" + "net" + "time" + + mysqldriver "github.com/go-sql-driver/mysql" +) + +func openMySQL(c ConnReq) (*sql.DB, error) { + host := c.Host + if host == "" { + host = "127.0.0.1" + } + port := c.Port + if port == 0 { + port = 3306 + } + user := c.User + if user == "" { + user = "root" + } + cfg := mysqldriver.NewConfig() + cfg.User = user + cfg.Passwd = c.Password + cfg.Net = "tcp" + cfg.Addr = net.JoinHostPort(host, fmt.Sprintf("%d", port)) + cfg.DBName = c.Database + cfg.ParseTime = true + cfg.Timeout = 5 * time.Second + cfg.ReadTimeout = 30 * time.Second + cfg.WriteTimeout = 30 * time.Second + cfg.Loc = time.Local + cfg.Params = map[string]string{"charset": "utf8mb4"} + mode := EffectiveSSLMode(c) + switch mode { + case "disable": + // no TLS + case "skip-verify": + if c.SSLCA == "" && c.SSLCert == "" && c.SSLKey == "" { + cfg.TLSConfig = "skip-verify" + } else { + name, err := registerMySQLTLS(c) + if err != nil { + return nil, err + } + cfg.TLSConfig = name + } + case "require", "verify-ca": + if c.SSLCA == "" && c.SSLCert == "" && c.SSLKey == "" && mode == "require" { + cfg.TLSConfig = "true" + } else { + name, err := registerMySQLTLS(c) + if err != nil { + return nil, err + } + cfg.TLSConfig = name + } + default: + return nil, fmt.Errorf("未知 SSL 模式: %s", mode) + } + return sql.Open("mysql", cfg.FormatDSN()) +} + +func registerMySQLTLS(c ConnReq) (string, error) { + tlsCfg, err := tlsConfigFrom(c) + if err != nil { + return "", err + } + if tlsCfg == nil { + return "", fmt.Errorf("SSL 已启用但未能构建 TLS 配置") + } + sum := sha256.Sum256([]byte(EffectiveSSLMode(c) + "|" + c.SSLCA + "|" + c.SSLCert + "|" + c.SSLKey + "|" + c.Host)) + name := "opsxdb-" + hex.EncodeToString(sum[:8]) + mysqldriver.DeregisterTLSConfig(name) + if err := mysqldriver.RegisterTLSConfig(name, tlsCfg); err != nil { + return "", fmt.Errorf("注册 MySQL TLS 配置失败: %w", err) + } + return name, nil +} + +func listDatabasesMySQL(db *sql.DB) ([]string, error) { + rows, err := db.Query("SHOW DATABASES") + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listTablesMySQL(db *sql.DB, database string) ([]string, error) { + q := "SHOW TABLES" + if database != "" { + q = "SHOW TABLES FROM " + quoteMySQLIdent(database) + } + rows, err := db.Query(q) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listColumnsMySQL(db *sql.DB, database, table string) ([]ColumnInfo, error) { + ident := quoteMySQLIdent(table) + if database != "" { + ident = quoteMySQLIdent(database) + "." + ident + } + rows, err := db.Query("SHOW FULL COLUMNS FROM " + ident) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ColumnInfo + for rows.Next() { + var field, typ, null, key string + var collation, extra, priv, comment sql.NullString + var def sql.NullString + if err := rows.Scan(&field, &typ, &collation, &null, &key, &def, &extra, &priv, &comment); err != nil { + return nil, err + } + col := ColumnInfo{Name: field, Type: typ, Nullable: null == "YES", Key: key, Comment: comment.String} + if def.Valid { + col.Default = def.String + } + out = append(out, col) + } + if out == nil { + out = []ColumnInfo{} + } + return out, rows.Err() +} diff --git a/apps/server/internal/db/open.go b/apps/server/internal/db/open.go new file mode 100644 index 0000000..6d03ffb --- /dev/null +++ b/apps/server/internal/db/open.go @@ -0,0 +1,85 @@ +package db + +import ( + "database/sql" + "fmt" + "strings" + "time" +) + +func driverKey(d string) string { + return strings.ToLower(strings.TrimSpace(d)) +} + +func isMySQLFamily(d string) bool { + switch driverKey(d) { + case "mysql", "mariadb", "tidb", "oceanbase", "polardb", "tdsql": + return true + default: + return false + } +} + +func isPostgresFamily(d string) bool { + switch driverKey(d) { + case "postgresql", "postgres", "gaussdb", "opengauss", "kingbase": + return true + default: + return false + } +} + +// Open 按驱动打开 *sql.DB +func Open(c ConnReq) (*sql.DB, error) { + var ( + db *sql.DB + err error + ) + switch { + case isMySQLFamily(c.Driver): + db, err = openMySQL(c) + case isPostgresFamily(c.Driver): + db, err = openPostgres(c) + case driverKey(c.Driver) == "sqlserver" || driverKey(c.Driver) == "mssql": + db, err = openMSSQL(c) + case driverKey(c.Driver) == "oracle": + db, err = openOracle(c) + case driverKey(c.Driver) == "sqlite": + db, err = openSQLite(c) + default: + return nil, fmt.Errorf("驱动「%s」尚未实现(当前支持: mysql/mariadb/tidb、postgresql、sqlserver、oracle、sqlite、redis)", c.Driver) + } + if err != nil { + return nil, err + } + db.SetConnMaxLifetime(2 * time.Minute) + db.SetMaxOpenConns(5) + db.SetMaxIdleConns(2) + return db, nil +} + +func TestConnection(c ConnReq) error { + dial, tun, err := ApplySSHTunnel(c) + if err != nil { + return err + } + if tun != nil { + defer tun.Close() + } + if err := testDial(dial); err != nil { + return fmt.Errorf("数据库: %w", err) + } + return nil +} + +func testDial(c ConnReq) error { + if isRedis(c.Driver) { + return testRedis(c) + } + db, err := Open(c) + if err != nil { + return err + } + defer db.Close() + return db.Ping() +} diff --git a/apps/server/internal/db/ops.go b/apps/server/internal/db/ops.go new file mode 100644 index 0000000..40177fc --- /dev/null +++ b/apps/server/internal/db/ops.go @@ -0,0 +1,238 @@ +package db + +import ( + "context" + "errors" + "fmt" + "strings" + "time" +) + +func ListDatabases(c ConnReq) ([]string, error) { + if isRedis(c.Driver) { + return listDatabasesRedis(c) + } + cc := c + switch { + case isMySQLFamily(c.Driver): + cc.Database = "" + case isPostgresFamily(c.Driver): + if cc.Database == "" { + cc.Database = "postgres" + } + case driverKey(c.Driver) == "sqlserver" || driverKey(c.Driver) == "mssql": + if cc.Database == "" { + cc.Database = "master" + } + } + db, err := Open(cc) + if err != nil { + return nil, err + } + defer db.Close() + switch { + case isMySQLFamily(c.Driver): + return listDatabasesMySQL(db) + case isPostgresFamily(c.Driver): + return listDatabasesPostgres(db) + case driverKey(c.Driver) == "sqlserver" || driverKey(c.Driver) == "mssql": + return listDatabasesMSSQL(db) + case driverKey(c.Driver) == "oracle": + return listDatabasesOracle(db) + case driverKey(c.Driver) == "sqlite": + return listDatabasesSQLite(db) + default: + return nil, fmt.Errorf("unsupported driver") + } +} + +func ListTables(c ConnReq) ([]string, error) { + if isRedis(c.Driver) { + return listTablesRedis(c) + } + db, err := Open(c) + if err != nil { + return nil, err + } + defer db.Close() + switch { + case isMySQLFamily(c.Driver): + return listTablesMySQL(db, c.Database) + case isPostgresFamily(c.Driver): + return listTablesPostgres(db, c.Database) + case driverKey(c.Driver) == "sqlserver" || driverKey(c.Driver) == "mssql": + return listTablesMSSQL(db, c.Database) + case driverKey(c.Driver) == "oracle": + return listTablesOracle(db, c.Database) + case driverKey(c.Driver) == "sqlite": + return listTablesSQLite(db, c.Database) + default: + return nil, fmt.Errorf("unsupported driver") + } +} + +func ListColumns(c ConnReq) ([]ColumnInfo, error) { + if c.Table == "" { + return nil, fmt.Errorf("缺少表名") + } + if isRedis(c.Driver) { + return listColumnsRedis(c, c.Table) + } + db, err := Open(c) + if err != nil { + return nil, err + } + defer db.Close() + switch { + case isMySQLFamily(c.Driver): + return listColumnsMySQL(db, c.Database, c.Table) + case isPostgresFamily(c.Driver): + return listColumnsPostgres(db, c.Database, c.Table) + case driverKey(c.Driver) == "sqlserver" || driverKey(c.Driver) == "mssql": + return listColumnsMSSQL(db, c.Database, c.Table) + case driverKey(c.Driver) == "oracle": + return listColumnsOracle(db, c.Database, c.Table) + case driverKey(c.Driver) == "sqlite": + return listColumnsSQLite(db, c.Database, c.Table) + default: + return nil, fmt.Errorf("unsupported driver") + } +} + +func rowLimit(c ConnReq) int { + n := c.Limit + if n <= 0 { + n = DefaultRowLimit + } + if n > MaxRowLimit { + n = MaxRowLimit + } + return n +} + +func errMsg(err error) string { + if err == nil { + return "" + } + if errors.Is(err, context.Canceled) { + return "已取消" + } + // client gone / deadline tied to request cancel + if errors.Is(err, context.DeadlineExceeded) { + return err.Error() + } + msg := err.Error() + if strings.Contains(msg, "context canceled") { + return "已取消" + } + return msg +} + +func execFail(start time.Time, err error) ExecResult { + return ExecResult{ + Columns: []string{}, + Rows: []map[string]interface{}{}, + TimeMs: time.Since(start).Milliseconds(), + Error: errMsg(err), + } +} + +// ExecSQL 执行查询 / 非查询语句。ctx 来自 HTTP 请求,客户端断开/Abort 时可取消 DB 调用。 +func ExecSQL(ctx context.Context, c ConnReq) ExecResult { + start := time.Now() + if ctx == nil { + ctx = context.Background() + } + if err := ctx.Err(); err != nil { + return execFail(start, err) + } + if strings.TrimSpace(c.SQL) == "" { + return ExecResult{Columns: []string{}, Rows: []map[string]interface{}{}, Error: "empty sql"} + } + if isRedis(c.Driver) { + return execRedis(ctx, c) + } + db, err := Open(c) + if err != nil { + return execFail(start, err) + } + defer db.Close() + + if !isQuerySQL(c.SQL) { + res, err := db.ExecContext(ctx, c.SQL) + if err != nil { + return execFail(start, err) + } + affected, _ := res.RowsAffected() + return ExecResult{ + Columns: []string{"affected_rows"}, + Rows: []map[string]interface{}{{"affected_rows": affected}}, + RowCount: 1, + TimeMs: time.Since(start).Milliseconds(), + } + } + + rows, err := db.QueryContext(ctx, c.SQL) + if err != nil { + return execFail(start, err) + } + defer rows.Close() + + cols, err := rows.Columns() + if err != nil { + return execFail(start, err) + } + limit := rowLimit(c) + var resultRows []map[string]interface{} + truncated := false + for rows.Next() { + if err := ctx.Err(); err != nil { + return execFail(start, err) + } + raw := make([]interface{}, len(cols)) + ptrs := make([]interface{}, len(cols)) + for i := range raw { + ptrs[i] = &raw[i] + } + if err := rows.Scan(ptrs...); err != nil { + return execFail(start, err) + } + m := make(map[string]interface{}, len(cols)) + for i, col := range cols { + m[col] = normalize(raw[i]) + } + resultRows = append(resultRows, m) + if len(resultRows) >= limit { + truncated = true + break + } + } + if err := rows.Err(); err != nil { + return execFail(start, err) + } + if resultRows == nil { + resultRows = []map[string]interface{}{} + } + return ExecResult{ + Columns: cols, + Rows: resultRows, + RowCount: len(resultRows), + TimeMs: time.Since(start).Milliseconds(), + Truncated: truncated, + Limit: limit, + } +} + +func isQuerySQL(sql string) bool { + s := stripSQLLead(sql) + if s == "" { + return true + } + up := strings.ToUpper(s) + for _, p := range []string{"SELECT", "SHOW", "DESCRIBE", "DESC", "EXPLAIN", "WITH", "PRAGMA"} { + if strings.HasPrefix(up, p+" ") || up == p || strings.HasPrefix(up, p+"\t") || strings.HasPrefix(up, p+"\n") { + return true + } + } + return false +} diff --git a/apps/server/internal/db/ops_test.go b/apps/server/internal/db/ops_test.go new file mode 100644 index 0000000..0cdd489 --- /dev/null +++ b/apps/server/internal/db/ops_test.go @@ -0,0 +1,39 @@ +package db + +import ( + "context" + "testing" +) + +func TestIsQuerySQL(t *testing.T) { + if !isQuerySQL("SELECT 1") { + t.Fatal("select") + } + if isQuerySQL("DELETE FROM t") { + t.Fatal("delete is not query") + } + if !isQuerySQL("-- hi\nSELECT 1") { + t.Fatal("comment then select") + } +} + +func TestIsDangerousSQL(t *testing.T) { + if !IsDangerousSQL("DROP TABLE users") { + t.Fatal("drop") + } + if !IsDangerousSQL("DELETE FROM users") { + t.Fatal("delete no where") + } + if IsDangerousSQL("DELETE FROM users WHERE id=1") { + t.Fatal("delete with where is ok") + } + if IsDangerousSQL("SELECT * FROM users") { + t.Fatal("select") + } +} + +func TestErrMsgCanceled(t *testing.T) { + if got := errMsg(context.Canceled); got != "已取消" { + t.Fatalf("got %q", got) + } +} diff --git a/apps/server/internal/db/oracle.go b/apps/server/internal/db/oracle.go new file mode 100644 index 0000000..d71d878 --- /dev/null +++ b/apps/server/internal/db/oracle.go @@ -0,0 +1,145 @@ +package db + +import ( + "database/sql" + "fmt" + "net/url" + + ora "github.com/sijms/go-ora/v2" +) + +func openOracle(c ConnReq) (*sql.DB, error) { + host := c.Host + if host == "" { + host = "127.0.0.1" + } + port := c.Port + if port == 0 { + port = 1521 + } + user := c.User + if user == "" { + user = "system" + } + service := c.Service + if service == "" { + if c.Database != "" { + service = c.Database + } else { + service = "XEPDB1" + } + } + dsn := fmt.Sprintf("oracle://%s:%s@%s:%d/%s", url.PathEscape(user), url.PathEscape(c.Password), host, port, service) + mode := EffectiveSSLMode(c) + if mode != "disable" { + q := url.Values{} + q.Set("SSL", "true") + if mode == "skip-verify" { + q.Set("SSL VERIFY", "false") + } else { + q.Set("SSL VERIFY", "true") + } + dsn = dsn + "?" + q.Encode() + } + + tlsCfg, err := tlsConfigFrom(c) + if err != nil { + return nil, err + } + if tlsCfg != nil { + connector := ora.NewConnector(dsn) + oc, ok := connector.(*ora.OracleConnector) + if !ok { + return nil, fmt.Errorf("oracle: connector 类型异常") + } + oc.WithTLSConfig(tlsCfg) + return sql.OpenDB(oc), nil + } + return sql.Open("oracle", dsn) +} + +func listDatabasesOracle(db *sql.DB) ([]string, error) { + // 返回当前用户可见 schema + rows, err := db.Query(`SELECT username FROM all_users ORDER BY username`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listTablesOracle(db *sql.DB, schema string) ([]string, error) { + var ( + rows *sql.Rows + err error + ) + if schema == "" { + rows, err = db.Query(`SELECT table_name FROM user_tables ORDER BY table_name`) + } else { + rows, err = db.Query(`SELECT table_name FROM all_tables WHERE owner = UPPER(:1) ORDER BY table_name`, schema) + } + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listColumnsOracle(dbh *sql.DB, schema, table string) ([]ColumnInfo, error) { + owner, t := splitSchemaTable(table) + if owner == "" { + owner = schema + } + var ( + rows *sql.Rows + err error + ) + if owner == "" { + rows, err = dbh.Query(` + SELECT column_name, data_type, nullable, data_default + FROM user_tab_columns WHERE table_name = UPPER(:1) + ORDER BY column_id`, t) + } else { + rows, err = dbh.Query(` + SELECT column_name, data_type, nullable, data_default + FROM all_tab_columns WHERE owner = UPPER(:1) AND table_name = UPPER(:2) + ORDER BY column_id`, owner, t) + } + if err != nil { + return nil, err + } + defer rows.Close() + var out []ColumnInfo + for rows.Next() { + var name, typ, nullable string + var def sql.NullString + if err := rows.Scan(&name, &typ, &nullable, &def); err != nil { + return nil, err + } + col := ColumnInfo{Name: name, Type: typ, Nullable: nullable == "Y"} + if def.Valid { + col.Default = def.String + } + out = append(out, col) + } + if out == nil { + out = []ColumnInfo{} + } + return out, rows.Err() +} diff --git a/apps/server/internal/db/postgres.go b/apps/server/internal/db/postgres.go new file mode 100644 index 0000000..f92aefb --- /dev/null +++ b/apps/server/internal/db/postgres.go @@ -0,0 +1,147 @@ +package db + +import ( + "database/sql" + "fmt" + "strings" + + _ "github.com/jackc/pgx/v5/stdlib" +) + +func openPostgres(c ConnReq) (*sql.DB, error) { + host := c.Host + if host == "" { + host = "127.0.0.1" + } + port := c.Port + if port == 0 { + port = 5432 + } + user := c.User + if user == "" { + user = "postgres" + } + dbName := c.Database + if dbName == "" { + dbName = "postgres" + } + mode := EffectiveSSLMode(c) + sslmode := "disable" + switch mode { + case "disable": + sslmode = "disable" + case "require", "skip-verify": + // libpq require ≈ encrypt without CA/hostname verify + sslmode = "require" + case "verify-ca": + sslmode = "verify-ca" + if strings.TrimSpace(c.SSLCA) == "" { + return nil, fmt.Errorf("verify-ca 模式需要填写 CA 证书路径") + } + default: + return nil, fmt.Errorf("未知 SSL 模式: %s", mode) + } + + var b strings.Builder + fmt.Fprintf(&b, "host=%s port=%d user=%s password=%s dbname=%s sslmode=%s connect_timeout=5", + pqEscape(host), port, pqEscape(user), pqEscape(c.Password), pqEscape(dbName), sslmode) + if ca := strings.TrimSpace(c.SSLCA); ca != "" { + fmt.Fprintf(&b, " sslrootcert=%s", pqEscape(ca)) + } + if cert := strings.TrimSpace(c.SSLCert); cert != "" { + fmt.Fprintf(&b, " sslcert=%s", pqEscape(cert)) + } + if key := strings.TrimSpace(c.SSLKey); key != "" { + fmt.Fprintf(&b, " sslkey=%s", pqEscape(key)) + } + return sql.Open("pgx", b.String()) +} + +func pqEscape(v string) string { + if v == "" { + return "''" + } + needs := strings.ContainsAny(v, " '\\") + if !needs { + return v + } + return "'" + strings.ReplaceAll(strings.ReplaceAll(v, `\`, `\\`), `'`, `\'`) + "'" +} + +func listDatabasesPostgres(db *sql.DB) ([]string, error) { + rows, err := db.Query(`SELECT datname FROM pg_database WHERE datistemplate = false ORDER BY 1`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listTablesPostgres(db *sql.DB, database string) ([]string, error) { + // 当前连接库下的 public 表;切库需重新 Open + _ = database + rows, err := db.Query(` + SELECT CASE WHEN schemaname = 'public' THEN tablename ELSE schemaname || '.' || tablename END + FROM pg_catalog.pg_tables + WHERE schemaname NOT IN ('pg_catalog','information_schema') + ORDER BY 1`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []string + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listColumnsPostgres(dbh *sql.DB, _, table string) ([]ColumnInfo, error) { + schema, t := splitSchemaTable(table) + if schema == "" { + schema = "public" + } + rows, err := dbh.Query(` + SELECT column_name, data_type, + COALESCE(character_maximum_length::text, numeric_precision::text, ''), + is_nullable, column_default + FROM information_schema.columns + WHERE table_schema = $1 AND table_name = $2 + ORDER BY ordinal_position`, schema, t) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ColumnInfo + for rows.Next() { + var name, typ, extra, nullable string + var def sql.NullString + if err := rows.Scan(&name, &typ, &extra, &nullable, &def); err != nil { + return nil, err + } + if extra != "" { + typ = typ + "(" + extra + ")" + } + col := ColumnInfo{Name: name, Type: typ, Nullable: nullable == "YES"} + if def.Valid { + col.Default = def.String + } + out = append(out, col) + } + if out == nil { + out = []ColumnInfo{} + } + return out, rows.Err() +} diff --git a/apps/server/internal/db/redis.go b/apps/server/internal/db/redis.go new file mode 100644 index 0000000..f20a59f --- /dev/null +++ b/apps/server/internal/db/redis.go @@ -0,0 +1,597 @@ +package db + +import ( + "context" + "fmt" + "strconv" + "strings" + "time" + + "github.com/redis/go-redis/v9" +) + +func redisOpts(c ConnReq) (*redis.Options, error) { + host := c.Host + if host == "" { + host = "127.0.0.1" + } + port := c.Port + if port == 0 { + port = 6379 + } + db := 0 + if c.Database != "" { + if n, err := strconv.Atoi(c.Database); err == nil { + db = n + } + } + opts := &redis.Options{ + Addr: fmt.Sprintf("%s:%d", host, port), + Password: c.Password, + Username: c.User, + DB: db, + DialTimeout: 5 * time.Second, + ReadTimeout: 10 * time.Second, + WriteTimeout: 10 * time.Second, + } + if SSLEnabled(c) { + tlsCfg, err := tlsConfigFrom(c) + if err != nil { + return nil, err + } + opts.TLSConfig = tlsCfg + } + return opts, nil +} + +func openRedis(c ConnReq) (*redis.Client, error) { + opts, err := redisOpts(c) + if err != nil { + return nil, err + } + return redis.NewClient(opts), nil +} + +func testRedis(c ConnReq) error { + cli, err := openRedis(c) + if err != nil { + return err + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + return cli.Ping(ctx).Err() +} + +func listDatabasesRedis(c ConnReq) ([]string, error) { + cli, err := openRedis(c) + if err != nil { + return nil, err + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + // INFO keyspace 可拿已用 DB;统一暴露 0-15 便于切换 + _ = ctx + out := make([]string, 16) + for i := 0; i < 16; i++ { + out[i] = strconv.Itoa(i) + } + return out, nil +} + +func listTablesRedis(c ConnReq) ([]string, error) { + cli, err := openRedis(c) + if err != nil { + return nil, err + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 8*time.Second) + defer cancel() + var keys []string + var cursor uint64 + for { + batch, next, err := cli.Scan(ctx, cursor, "*", 200).Result() + if err != nil { + return nil, err + } + keys = append(keys, batch...) + cursor = next + if cursor == 0 || len(keys) >= 500 { + break + } + } + if keys == nil { + keys = []string{} + } + return keys, nil +} + +func execRedis(parent context.Context, c ConnReq) ExecResult { + start := time.Now() + sql := strings.TrimSpace(c.SQL) + if sql == "" { + return ExecResult{Error: "empty sql", TimeMs: 0} + } + cli, err := openRedis(c) + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + defer cli.Close() + if parent == nil { + parent = context.Background() + } + ctx, cancel := context.WithTimeout(parent, 10*time.Second) + defer cancel() + + fields := splitRedisCmd(sql) + if len(fields) == 0 { + return ExecResult{Error: "empty command", TimeMs: time.Since(start).Milliseconds()} + } + cmd := strings.ToUpper(fields[0]) + args := fields[1:] + + switch cmd { + case "PING": + val, err := cli.Ping(ctx).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"result"}, Rows: []map[string]interface{}{{"result": val}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + case "GET": + if len(args) < 1 { + return ExecResult{Error: "GET key", TimeMs: time.Since(start).Milliseconds()} + } + val, err := cli.Get(ctx, args[0]).Result() + if err == redis.Nil { + return ExecResult{Columns: []string{"key", "value"}, Rows: []map[string]interface{}{}, RowCount: 0, TimeMs: time.Since(start).Milliseconds()} + } + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"key", "value"}, Rows: []map[string]interface{}{{"key": args[0], "value": val}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + case "SET": + if len(args) < 2 { + return ExecResult{Error: "SET key value", TimeMs: time.Since(start).Milliseconds()} + } + if err := cli.Set(ctx, args[0], strings.Join(args[1:], " "), 0).Err(); err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"affected_rows"}, Rows: []map[string]interface{}{{"affected_rows": 1}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + case "DEL": + if len(args) < 1 { + return ExecResult{Error: "DEL key [key...]", TimeMs: time.Since(start).Milliseconds()} + } + n, err := cli.Del(ctx, args...).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"affected_rows"}, Rows: []map[string]interface{}{{"affected_rows": n}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + case "KEYS": + pat := "*" + if len(args) > 0 { + pat = args[0] + } + keys, err := cli.Keys(ctx, pat).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + rows := make([]map[string]interface{}, 0, len(keys)) + for _, k := range keys { + rows = append(rows, map[string]interface{}{"key": k}) + } + return ExecResult{Columns: []string{"key"}, Rows: rows, RowCount: len(rows), TimeMs: time.Since(start).Milliseconds()} + case "SCAN": + keys, err := listTablesRedis(c) + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + rows := make([]map[string]interface{}, 0, len(keys)) + for _, k := range keys { + rows = append(rows, map[string]interface{}{"key": k}) + } + return ExecResult{Columns: []string{"key"}, Rows: rows, RowCount: len(rows), TimeMs: time.Since(start).Milliseconds()} + case "HGETALL": + if len(args) < 1 { + return ExecResult{Error: "HGETALL key", TimeMs: time.Since(start).Milliseconds()} + } + c2 := c + c2.Key = args[0] + c2.Limit = redisValueRowCap + v := GetRedisKey(c2) + if v.Error != "" && v.Type == "" { + return ExecResult{Error: v.Error, TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error} + case "LRANGE": + if len(args) < 1 { + return ExecResult{Error: "LRANGE key [start stop]", TimeMs: time.Since(start).Milliseconds()} + } + c2 := c + c2.Key = args[0] + c2.Limit = redisValueRowCap + v := GetRedisKey(c2) + return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error} + case "SMEMBERS": + if len(args) < 1 { + return ExecResult{Error: "SMEMBERS key", TimeMs: time.Since(start).Milliseconds()} + } + c2 := c + c2.Key = args[0] + v := GetRedisKey(c2) + return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error} + case "ZRANGE": + if len(args) < 1 { + return ExecResult{Error: "ZRANGE key [start stop]", TimeMs: time.Since(start).Milliseconds()} + } + c2 := c + c2.Key = args[0] + v := GetRedisKey(c2) + return ExecResult{Columns: v.Columns, Rows: v.Rows, RowCount: v.RowCount, TimeMs: time.Since(start).Milliseconds(), Truncated: v.Truncated, Limit: v.Limit, Error: v.Error} + case "TYPE": + if len(args) < 1 { + return ExecResult{Error: "TYPE key", TimeMs: time.Since(start).Milliseconds()} + } + t, err := cli.Type(ctx, args[0]).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"key", "type"}, Rows: []map[string]interface{}{{"key": args[0], "type": t}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + case "TTL": + if len(args) < 1 { + return ExecResult{Error: "TTL key", TimeMs: time.Since(start).Milliseconds()} + } + n, err := cli.TTL(ctx, args[0]).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{Columns: []string{"key", "ttl_sec"}, Rows: []map[string]interface{}{{"key": args[0], "ttl_sec": int64(n.Seconds())}}, RowCount: 1, TimeMs: time.Since(start).Milliseconds()} + default: + return ExecResult{Error: fmt.Sprintf("暂不支持命令 %s(可用: PING/GET/SET/DEL/KEYS/SCAN/TYPE/TTL/HGETALL/LRANGE/SMEMBERS/ZRANGE)", cmd), TimeMs: time.Since(start).Milliseconds()} + } +} + +func splitRedisCmd(s string) []string { + var out []string + var cur strings.Builder + inQ := false + esc := false + for _, r := range s { + switch { + case esc: + cur.WriteRune(r) + esc = false + case r == '\\': + esc = true + case r == '"': + inQ = !inQ + case !inQ && (r == ' ' || r == '\t' || r == '\n'): + if cur.Len() > 0 { + out = append(out, cur.String()) + cur.Reset() + } + default: + cur.WriteRune(r) + } + } + if cur.Len() > 0 { + out = append(out, cur.String()) + } + return out +} + +func isRedis(d string) bool { + return driverKey(d) == "redis" +} + +// IsRedisDriver exported for router guards. +func IsRedisDriver(d string) bool { + return isRedis(d) +} + +func listColumnsRedis(c ConnReq, key string) ([]ColumnInfo, error) { + cli, err := openRedis(c) + if err != nil { + return nil, err + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + typ, err := cli.Type(ctx, key).Result() + if err != nil { + return nil, err + } + ttl, _ := cli.TTL(ctx, key).Result() + return []ColumnInfo{ + {Name: key, Type: typ, Comment: fmt.Sprintf("ttl=%ds", int64(ttl.Seconds()))}, + }, nil +} + +const redisKeyListCap = 500 +const redisValueRowCap = 500 + +func redisKeyName(c ConnReq) string { + if c.Key != "" { + return c.Key + } + return c.Table +} + +func redisScanPattern(c ConnReq) string { + p := strings.TrimSpace(c.Pattern) + if p == "" { + return "*" + } + return p +} + +func redisValueLimit(c ConnReq) int { + n := c.Limit + if n <= 0 { + n = redisValueRowCap + } + if n > MaxRowLimit { + n = MaxRowLimit + } + return n +} + +func ttlSeconds(d time.Duration) int64 { + // go-redis maps Redis TTL -1 (no expire) / -2 (missing) to -1ns/-2ns + // (some versions use -1s/-2s). Truncating with d/time.Second turns those into 0. + switch d { + case time.Duration(-1), -1 * time.Second: + return -1 + case time.Duration(-2), -2 * time.Second: + return -2 + } + return int64(d / time.Second) +} + +// ListRedisKeys SCAN + pipeline TYPE/TTL. Cap ~500. +func ListRedisKeys(c ConnReq) RedisKeysResult { + start := time.Now() + cli, err := openRedis(c) + if err != nil { + return RedisKeysResult{Keys: []RedisKeyInfo{}, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 12*time.Second) + defer cancel() + + pat := redisScanPattern(c) + var names []string + var cursor uint64 + truncated := false + for { + batch, next, err := cli.Scan(ctx, cursor, pat, 200).Result() + if err != nil { + return RedisKeysResult{Keys: []RedisKeyInfo{}, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + names = append(names, batch...) + cursor = next + if len(names) >= redisKeyListCap { + truncated = true + names = names[:redisKeyListCap] + break + } + if cursor == 0 { + break + } + } + if names == nil { + names = []string{} + } + + out := make([]RedisKeyInfo, len(names)) + if len(names) == 0 { + return RedisKeysResult{Keys: out, Truncated: truncated, TimeMs: time.Since(start).Milliseconds()} + } + + // Pipeline TYPE + TTL (avoid N+1) + pipe := cli.Pipeline() + typeCmds := make([]*redis.StatusCmd, len(names)) + ttlCmds := make([]*redis.DurationCmd, len(names)) + for i, k := range names { + typeCmds[i] = pipe.Type(ctx, k) + ttlCmds[i] = pipe.TTL(ctx, k) + } + _, _ = pipe.Exec(ctx) + for i, k := range names { + typ := "none" + if t, err := typeCmds[i].Result(); err == nil { + typ = t + } + ttl := int64(-2) + if d, err := ttlCmds[i].Result(); err == nil { + ttl = ttlSeconds(d) + } + out[i] = RedisKeyInfo{Name: k, Type: typ, TTL: ttl} + } + return RedisKeysResult{Keys: out, Truncated: truncated, TimeMs: time.Since(start).Milliseconds()} +} + +// GetRedisKey typed value → ResultPane columns/rows. +func GetRedisKey(c ConnReq) RedisKeyValue { + start := time.Now() + key := redisKeyName(c) + if key == "" { + return RedisKeyValue{Error: "缺少 key", TimeMs: time.Since(start).Milliseconds()} + } + cli, err := openRedis(c) + if err != nil { + return RedisKeyValue{Name: key, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) + defer cancel() + + typ, err := cli.Type(ctx, key).Result() + if err != nil { + return RedisKeyValue{Name: key, Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + ttl := int64(-2) + if d, err := cli.TTL(ctx, key).Result(); err == nil { + ttl = ttlSeconds(d) + } + limit := redisValueLimit(c) + base := RedisKeyValue{Name: key, Type: typ, TTL: ttl, Limit: limit} + + switch typ { + case "none": + base.Columns = []string{"value"} + base.Rows = []map[string]interface{}{} + base.RowCount = 0 + base.Error = "key 不存在" + base.TimeMs = time.Since(start).Milliseconds() + return base + case "string": + val, err := cli.Get(ctx, key).Result() + if err == redis.Nil { + base.Columns = []string{"value"} + base.Rows = []map[string]interface{}{} + base.RowCount = 0 + base.TimeMs = time.Since(start).Milliseconds() + return base + } + if err != nil { + base.Error = errMsg(err) + base.TimeMs = time.Since(start).Milliseconds() + return base + } + base.Columns = []string{"value"} + base.Rows = []map[string]interface{}{{"value": val}} + base.RowCount = 1 + base.TimeMs = time.Since(start).Milliseconds() + return base + case "hash": + fields, err := cli.HGetAll(ctx, key).Result() + if err != nil { + base.Error = errMsg(err) + base.TimeMs = time.Since(start).Milliseconds() + return base + } + base.Columns = []string{"field", "value"} + rows := make([]map[string]interface{}, 0, len(fields)) + for f, v := range fields { + rows = append(rows, map[string]interface{}{"field": f, "value": v}) + if len(rows) >= limit { + base.Truncated = true + break + } + } + base.Rows = rows + base.RowCount = len(rows) + base.TimeMs = time.Since(start).Milliseconds() + return base + case "list": + vals, err := cli.LRange(ctx, key, 0, int64(limit)-1).Result() + if err != nil { + base.Error = errMsg(err) + base.TimeMs = time.Since(start).Milliseconds() + return base + } + truncated := false + n, _ := cli.LLen(ctx, key).Result() + if n > int64(len(vals)) { + truncated = true + } + base.Columns = []string{"index", "value"} + rows := make([]map[string]interface{}, 0, len(vals)) + for i, v := range vals { + rows = append(rows, map[string]interface{}{"index": i, "value": v}) + } + base.Rows = rows + base.RowCount = len(rows) + base.Truncated = truncated + base.TimeMs = time.Since(start).Milliseconds() + return base + case "set": + base.Columns = []string{"member"} + var rows []map[string]interface{} + var scursor uint64 + truncated := false + for { + batch, next, err := cli.SScan(ctx, key, scursor, "*", 200).Result() + if err != nil { + base.Error = errMsg(err) + base.TimeMs = time.Since(start).Milliseconds() + return base + } + for _, m := range batch { + rows = append(rows, map[string]interface{}{"member": m}) + if len(rows) >= limit { + truncated = true + break + } + } + scursor = next + if truncated || scursor == 0 { + break + } + } + if rows == nil { + rows = []map[string]interface{}{} + } + base.Rows = rows + base.RowCount = len(rows) + base.Truncated = truncated + base.TimeMs = time.Since(start).Milliseconds() + return base + case "zset": + vals, err := cli.ZRangeWithScores(ctx, key, 0, int64(limit-1)).Result() + if err != nil { + base.Error = errMsg(err) + base.TimeMs = time.Since(start).Milliseconds() + return base + } + truncated := false + n, _ := cli.ZCard(ctx, key).Result() + if n > int64(len(vals)) { + truncated = true + } + base.Columns = []string{"score", "member"} + rows := make([]map[string]interface{}, 0, len(vals)) + for _, z := range vals { + rows = append(rows, map[string]interface{}{"score": z.Score, "member": z.Member}) + } + base.Rows = rows + base.RowCount = len(rows) + base.Truncated = truncated + base.TimeMs = time.Since(start).Milliseconds() + return base + default: + // stream / recon / module types: meta only + base.Columns = []string{"info"} + base.Rows = []map[string]interface{}{{"info": fmt.Sprintf("类型 %s 暂不展开,可用命令查看", typ)}} + base.RowCount = 1 + base.TimeMs = time.Since(start).Milliseconds() + return base + } +} + +// DeleteRedisKey DEL one key. +func DeleteRedisKey(c ConnReq) ExecResult { + start := time.Now() + key := redisKeyName(c) + if key == "" { + return ExecResult{Error: "缺少 key", TimeMs: time.Since(start).Milliseconds()} + } + cli, err := openRedis(c) + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + defer cli.Close() + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + n, err := cli.Del(ctx, key).Result() + if err != nil { + return ExecResult{Error: errMsg(err), TimeMs: time.Since(start).Milliseconds()} + } + return ExecResult{ + Columns: []string{"affected_rows"}, + Rows: []map[string]interface{}{{"affected_rows": n}}, + RowCount: 1, + TimeMs: time.Since(start).Milliseconds(), + } +} diff --git a/apps/server/internal/db/redis_keys_test.go b/apps/server/internal/db/redis_keys_test.go new file mode 100644 index 0000000..32c06bb --- /dev/null +++ b/apps/server/internal/db/redis_keys_test.go @@ -0,0 +1,86 @@ +package db + +import ( + "net" + "strconv" + "testing" + + "github.com/alicebob/miniredis/v2" +) + +func TestListGetDeleteRedisKeys(t *testing.T) { + mr, err := miniredis.Run() + if err != nil { + t.Fatal(err) + } + defer mr.Close() + + _ = mr.Set("str:a", "hello") + mr.HSet("hash:b", "f1", "v1") + mr.Push("list:c", "x", "y") + mr.SAdd("set:d", "m1", "m2") + _, _ = mr.ZAdd("zset:e", 1.5, "zm") + + host, portStr, err := net.SplitHostPort(mr.Addr()) + if err != nil { + t.Fatal(err) + } + port, _ := strconv.Atoi(portStr) + base := ConnReq{Driver: "redis", Host: host, Port: port, Database: "0"} + + list := ListRedisKeys(base) + if list.Error != "" { + t.Fatal(list.Error) + } + if len(list.Keys) < 5 { + t.Fatalf("want >=5 keys, got %d %#v", len(list.Keys), list.Keys) + } + byName := map[string]RedisKeyInfo{} + for _, k := range list.Keys { + byName[k.Name] = k + } + if byName["str:a"].Type != "string" { + t.Fatalf("str type %s", byName["str:a"].Type) + } + if byName["hash:b"].Type != "hash" { + t.Fatalf("hash type %s", byName["hash:b"].Type) + } + // Real Redis returns -1 (no expire); miniredis may report 0. + if byName["str:a"].TTL < -1 { + t.Fatalf("unexpected ttl %d", byName["str:a"].TTL) + } + + gv := GetRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "str:a"}) + if gv.Error != "" || gv.RowCount != 1 || gv.Rows[0]["value"] != "hello" { + t.Fatalf("get string %#v", gv) + } + gh := GetRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "hash:b"}) + if gh.Error != "" || gh.RowCount < 1 || gh.Columns[0] != "field" { + t.Fatalf("get hash %#v", gh) + } + gl := GetRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "list:c"}) + if gl.Error != "" || gl.RowCount < 2 { + t.Fatalf("get list %#v", gl) + } + gs := GetRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "set:d"}) + if gs.Error != "" || gs.RowCount < 2 { + t.Fatalf("get set %#v", gs) + } + gz := GetRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "zset:e"}) + if gz.Error != "" || gz.RowCount != 1 { + t.Fatalf("get zset %#v", gz) + } + + pat := ListRedisKeys(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Pattern: "str:*"}) + if pat.Error != "" || len(pat.Keys) != 1 || pat.Keys[0].Name != "str:a" { + t.Fatalf("pattern %#v", pat) + } + + del := DeleteRedisKey(ConnReq{Driver: "redis", Host: host, Port: port, Database: "0", Key: "str:a"}) + if del.Error != "" { + t.Fatal(del.Error) + } + if mr.Exists("str:a") { + t.Fatal("key should be deleted") + } +} diff --git a/apps/server/internal/db/redis_ttl_test.go b/apps/server/internal/db/redis_ttl_test.go new file mode 100644 index 0000000..ec2a6c1 --- /dev/null +++ b/apps/server/internal/db/redis_ttl_test.go @@ -0,0 +1,29 @@ +package db + +import ( + "testing" + "time" +) + +func TestTTLSecondsSentinels(t *testing.T) { + cases := []struct { + in time.Duration + want int64 + }{ + {time.Duration(-1), -1}, + {-1 * time.Nanosecond, -1}, + {-1 * time.Second, -1}, + {time.Duration(-2), -2}, + {-2 * time.Nanosecond, -2}, + {-2 * time.Second, -2}, + {0, 0}, + {5 * time.Second, 5}, + {1500 * time.Millisecond, 1}, + } + for _, c := range cases { + got := ttlSeconds(c.in) + if got != c.want { + t.Fatalf("ttlSeconds(%v)=%d want %d", c.in, got, c.want) + } + } +} diff --git a/apps/server/internal/db/session.go b/apps/server/internal/db/session.go new file mode 100644 index 0000000..ee560f0 --- /dev/null +++ b/apps/server/internal/db/session.go @@ -0,0 +1,180 @@ +package db + +import ( + "crypto/rand" + "database/sql" + "encoding/hex" + "fmt" + "sync" + "time" + + "github.com/redis/go-redis/v9" +) + +const sessionTTL = 45 * time.Minute +const maxSessions = 64 + +type Session struct { + ID string + Conn ConnReq + mu sync.Mutex + sqlPool map[string]*sql.DB + redisCli *redis.Client + tunnel *SSHTunnel + LastUsed time.Time +} + +var ( + sessionsMu sync.Mutex + sessions = map[string]*Session{} +) + +func init() { + go func() { + t := time.NewTicker(2 * time.Minute) + defer t.Stop() + for range t.C { + expireSessions() + } + }() +} + +func newSessionID() string { + var b [16]byte + if _, err := rand.Read(b[:]); err != nil { + return fmt.Sprintf("%d", time.Now().UnixNano()) + } + return hex.EncodeToString(b[:]) +} + +func OpenSession(c ConnReq) (string, error) { + c.SQL = "" + c.Table = "" + c.Key = "" + c.Pattern = "" + c.SessionID = "" + + dial, tun, err := ApplySSHTunnel(c) + if err != nil { + return "", err + } + if err := testDial(dial); err != nil { + if tun != nil { + tun.Close() + } + return "", fmt.Errorf("数据库: %w", err) + } + + sessionsMu.Lock() + if len(sessions) >= maxSessions { + sessionsMu.Unlock() + if tun != nil { + tun.Close() + } + return "", fmt.Errorf("连接会话已满,请关闭不用的连接后重试") + } + id := newSessionID() + // Persist dialed endpoints (127.0.0.1:local) so later ops reuse the tunnel. + stored := dial + stored.SSHEnabled = false + s := &Session{ + ID: id, + Conn: stored, + sqlPool: map[string]*sql.DB{}, + tunnel: tun, + LastUsed: time.Now(), + } + sessions[id] = s + sessionsMu.Unlock() + return id, nil +} + +func GetSession(id string) (*Session, error) { + if id == "" { + return nil, fmt.Errorf("缺少 sessionId") + } + sessionsMu.Lock() + s, ok := sessions[id] + sessionsMu.Unlock() + if !ok { + return nil, fmt.Errorf("会话已失效,请重新连接") + } + s.mu.Lock() + s.LastUsed = time.Now() + s.mu.Unlock() + return s, nil +} + +func CloseSession(id string) { + sessionsMu.Lock() + s, ok := sessions[id] + if ok { + delete(sessions, id) + } + sessionsMu.Unlock() + if ok { + s.closeAll() + } +} + +func expireSessions() { + cutoff := time.Now().Add(-sessionTTL) + sessionsMu.Lock() + var dead []*Session + for id, s := range sessions { + s.mu.Lock() + stale := s.LastUsed.Before(cutoff) + s.mu.Unlock() + if stale { + dead = append(dead, s) + delete(sessions, id) + } + } + sessionsMu.Unlock() + for _, s := range dead { + s.closeAll() + } +} + +func (s *Session) closeAll() { + s.mu.Lock() + defer s.mu.Unlock() + for k, dbh := range s.sqlPool { + _ = dbh.Close() + delete(s.sqlPool, k) + } + if s.redisCli != nil { + _ = s.redisCli.Close() + s.redisCli = nil + } + if s.tunnel != nil { + s.tunnel.Close() + s.tunnel = nil + } +} + +func (s *Session) ConnFor(req ConnReq) ConnReq { + c := s.Conn + // SQLite 的 database 是文件路径,对象树里的 "main" 不能覆盖它 + if req.Database != "" && driverKey(c.Driver) != "sqlite" { + c.Database = req.Database + } + c.SQL = req.SQL + c.Table = req.Table + c.Key = req.Key + c.Pattern = req.Pattern + c.Limit = req.Limit + c.SessionID = s.ID + return c +} + +func ResolveConn(req ConnReq) (ConnReq, error) { + if req.SessionID == "" { + return req, nil + } + s, err := GetSession(req.SessionID) + if err != nil { + return req, err + } + return s.ConnFor(req), nil +} diff --git a/apps/server/internal/db/sqlite.go b/apps/server/internal/db/sqlite.go new file mode 100644 index 0000000..44ba578 --- /dev/null +++ b/apps/server/internal/db/sqlite.go @@ -0,0 +1,70 @@ +package db + +import ( + "database/sql" + "fmt" + + _ "modernc.org/sqlite" +) + +func openSQLite(c ConnReq) (*sql.DB, error) { + path := c.Database + if path == "" { + path = c.Host + } + if path == "" { + return nil, fmt.Errorf("SQLite 需要 database 填文件路径") + } + return sql.Open("sqlite", path) +} + +func listDatabasesSQLite(_ *sql.DB) ([]string, error) { + return []string{"main"}, nil +} + +func listTablesSQLite(db *sql.DB, _ string) ([]string, error) { + rows, err := db.Query(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY 1`) + if err != nil { + return nil, err + } + defer rows.Close() + out := []string{} + for rows.Next() { + var name string + if err := rows.Scan(&name); err != nil { + return nil, err + } + out = append(out, name) + } + return out, rows.Err() +} + +func listColumnsSQLite(dbh *sql.DB, _, table string) ([]ColumnInfo, error) { + rows, err := dbh.Query(`PRAGMA table_info(` + quotePGIdent(table) + `)`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ColumnInfo + for rows.Next() { + var cid int + var name, typ string + var notnull, pk int + var def sql.NullString + if err := rows.Scan(&cid, &name, &typ, ¬null, &def, &pk); err != nil { + return nil, err + } + col := ColumnInfo{Name: name, Type: typ, Nullable: notnull == 0} + if pk > 0 { + col.Key = "PRI" + } + if def.Valid { + col.Default = def.String + } + out = append(out, col) + } + if out == nil { + out = []ColumnInfo{} + } + return out, rows.Err() +} diff --git a/apps/server/internal/db/ssh_tunnel.go b/apps/server/internal/db/ssh_tunnel.go new file mode 100644 index 0000000..b9cef49 --- /dev/null +++ b/apps/server/internal/db/ssh_tunnel.go @@ -0,0 +1,216 @@ +package db + +import ( + "fmt" + "io" + "net" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "golang.org/x/crypto/ssh" +) + +// SSHTunnel holds a local TCP listener that forwards through an SSH session +// to the remote database host:port (local port forward semantics). +type SSHTunnel struct { + LocalPort int + listener net.Listener + client *ssh.Client + cancel chan struct{} + wg sync.WaitGroup + once sync.Once +} + +func (t *SSHTunnel) Close() { + if t == nil { + return + } + t.once.Do(func() { + close(t.cancel) + if t.listener != nil { + _ = t.listener.Close() + } + if t.client != nil { + _ = t.client.Close() + } + t.wg.Wait() + }) +} + +// StartLocalForward dials SSH and listens on 127.0.0.1:0, forwarding to c.Host:c.Port. +func StartLocalForward(c ConnReq) (*SSHTunnel, error) { + sshHost := strings.TrimSpace(c.SSHHost) + if sshHost == "" { + return nil, fmt.Errorf("请填写 SSH 主机") + } + sshPort := c.SSHPort + if sshPort <= 0 { + sshPort = 22 + } + sshUser := strings.TrimSpace(c.SSHUser) + if sshUser == "" { + return nil, fmt.Errorf("请填写 SSH 用户") + } + + auth, err := sshAuthMethods(c) + if err != nil { + return nil, err + } + + cfg := &ssh.ClientConfig{ + User: sshUser, + Auth: auth, + HostKeyCallback: ssh.InsecureIgnoreHostKey(), //nolint:gosec // product tunnel; host-key pinning later + Timeout: 12 * time.Second, + } + + addr := net.JoinHostPort(sshHost, fmt.Sprintf("%d", sshPort)) + client, err := ssh.Dial("tcp", addr, cfg) + if err != nil { + return nil, fmt.Errorf("无法连接跳板机 %s: %w", addr, err) + } + + ln, err := net.Listen("tcp", "127.0.0.1:0") + if err != nil { + _ = client.Close() + return nil, fmt.Errorf("本地监听失败: %w", err) + } + localPort := ln.Addr().(*net.TCPAddr).Port + + remoteHost := c.Host + if remoteHost == "" { + remoteHost = "127.0.0.1" + } + remotePort := c.Port + if remotePort <= 0 { + remotePort = 3306 + } + remoteAddr := net.JoinHostPort(remoteHost, fmt.Sprintf("%d", remotePort)) + + tun := &SSHTunnel{ + LocalPort: localPort, + listener: ln, + client: client, + cancel: make(chan struct{}), + } + + tun.wg.Add(1) + go func() { + defer tun.wg.Done() + for { + conn, err := ln.Accept() + if err != nil { + select { + case <-tun.cancel: + return + default: + return + } + } + tun.wg.Add(1) + go func(local net.Conn) { + defer tun.wg.Done() + defer local.Close() + remote, err := client.Dial("tcp", remoteAddr) + if err != nil { + return + } + defer remote.Close() + pipe(local, remote) + }(conn) + } + }() + + return tun, nil +} + +func sshAuthMethods(c ConnReq) ([]ssh.AuthMethod, error) { + authMode := strings.ToLower(strings.TrimSpace(c.SSHAuth)) + if authMode == "" { + authMode = "password" + } + switch authMode { + case "password": + if c.SSHPassword == "" { + return nil, fmt.Errorf("请填写 SSH 密码") + } + return []ssh.AuthMethod{ssh.Password(c.SSHPassword)}, nil + case "privatekey", "key", "private_key": + keyMaterial, err := loadSSHPrivateKey(c.SSHPrivateKey) + if err != nil { + return nil, err + } + var signer ssh.Signer + if strings.TrimSpace(c.SSHPassphrase) != "" { + signer, err = ssh.ParsePrivateKeyWithPassphrase(keyMaterial, []byte(c.SSHPassphrase)) + } else { + signer, err = ssh.ParsePrivateKey(keyMaterial) + } + if err != nil { + return nil, fmt.Errorf("解析私钥失败: %w", err) + } + return []ssh.AuthMethod{ssh.PublicKeys(signer)}, nil + default: + return nil, fmt.Errorf("不支持的 SSH 认证方式: %s", c.SSHAuth) + } +} + +func loadSSHPrivateKey(raw string) ([]byte, error) { + s := strings.TrimSpace(raw) + if s == "" { + return nil, fmt.Errorf("请填写私钥 PEM 或路径") + } + if strings.Contains(s, "BEGIN") && strings.Contains(s, "PRIVATE KEY") { + return []byte(s), nil + } + path := s + if strings.HasPrefix(path, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return nil, fmt.Errorf("展开 ~ 失败: %w", err) + } + path = filepath.Join(home, path[2:]) + } + b, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("读取私钥文件失败: %w", err) + } + return b, nil +} + +func pipe(a, b net.Conn) { + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + _, _ = io.Copy(a, b) + _ = a.Close() + }() + go func() { + defer wg.Done() + _, _ = io.Copy(b, a) + _ = b.Close() + }() + wg.Wait() +} + +// ApplySSHTunnel starts a local forward when sshEnabled and rewrites Host/Port to 127.0.0.1:. +// Caller must Close the returned tunnel (may be nil). +func ApplySSHTunnel(c ConnReq) (ConnReq, *SSHTunnel, error) { + if !c.SSHEnabled { + return c, nil, nil + } + tun, err := StartLocalForward(c) + if err != nil { + return c, nil, fmt.Errorf("SSH: %w", err) + } + out := c + out.Host = "127.0.0.1" + out.Port = tun.LocalPort + // Avoid nested tunnel if helpers re-enter + out.SSHEnabled = false + return out, tun, nil +} diff --git a/apps/server/internal/db/tls.go b/apps/server/internal/db/tls.go new file mode 100644 index 0000000..3d1e131 --- /dev/null +++ b/apps/server/internal/db/tls.go @@ -0,0 +1,115 @@ +package db + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "strings" +) + +// EffectiveSSLMode resolves ssl / sslMode for drivers. +// Checkbox off or mode disable → disable. Legacy ssl:true with empty mode → require. +func EffectiveSSLMode(c ConnReq) string { + mode := strings.ToLower(strings.TrimSpace(c.SSLMode)) + switch mode { + case "disable", "require", "skip-verify", "verify-ca": + if mode == "disable" { + return "disable" + } + return mode + } + if c.SSL { + return "require" + } + return "disable" +} + +// SSLEnabled is true when TLS should be negotiated. +func SSLEnabled(c ConnReq) bool { + return EffectiveSSLMode(c) != "disable" +} + +// tlsConfigFrom builds a *tls.Config from ConnReq paths/mode. +// Returns (nil, nil) when SSL is disabled. +func tlsConfigFrom(c ConnReq) (*tls.Config, error) { + mode := EffectiveSSLMode(c) + if mode == "disable" { + return nil, nil + } + + cfg := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + if host := strings.TrimSpace(c.Host); host != "" { + cfg.ServerName = host + } + + switch mode { + case "skip-verify": + cfg.InsecureSkipVerify = true + case "require": + // Encrypt; verify with system / provided CAs when present. + case "verify-ca": + // Prefer explicit CA; still verify chain (hostname via ServerName when set). + default: + return nil, fmt.Errorf("未知 SSL 模式: %s", mode) + } + + if path := strings.TrimSpace(c.SSLCA); path != "" { + pem, err := os.ReadFile(path) + if err != nil { + return nil, fmt.Errorf("读取 CA 证书失败 (%s): %w", path, err) + } + pool := x509.NewCertPool() + if !pool.AppendCertsFromPEM(pem) { + return nil, fmt.Errorf("CA 证书不是有效 PEM: %s", path) + } + cfg.RootCAs = pool + if mode == "verify-ca" { + // verify-ca: trust given CA but allow hostname mismatch (common for IP hosts) + cfg.InsecureSkipVerify = true + roots := pool + cfg.VerifyPeerCertificate = func(rawCerts [][]byte, _ [][]*x509.Certificate) error { + if len(rawCerts) == 0 { + return fmt.Errorf("服务端未提供证书") + } + certs := make([]*x509.Certificate, 0, len(rawCerts)) + for i, raw := range rawCerts { + cert, err := x509.ParseCertificate(raw) + if err != nil { + return fmt.Errorf("解析服务端证书失败: %w", err) + } + if i == 0 { + // keep leaf for Intermediates below + } + certs = append(certs, cert) + } + leaf := certs[0] + inter := x509.NewCertPool() + for _, cert := range certs[1:] { + inter.AddCert(cert) + } + _, err := leaf.Verify(x509.VerifyOptions{Roots: roots, Intermediates: inter}) + return err + } + } + } else if mode == "verify-ca" { + return nil, fmt.Errorf("verify-ca 模式需要填写 CA 证书路径") + } + + certPath := strings.TrimSpace(c.SSLCert) + keyPath := strings.TrimSpace(c.SSLKey) + if certPath != "" || keyPath != "" { + if certPath == "" || keyPath == "" { + return nil, fmt.Errorf("客户端证书与密钥需同时填写") + } + cert, err := tls.LoadX509KeyPair(certPath, keyPath) + if err != nil { + return nil, fmt.Errorf("加载客户端证书失败: %w", err) + } + cfg.Certificates = []tls.Certificate{cert} + } + + return cfg, nil +} diff --git a/apps/server/internal/db/tls_test.go b/apps/server/internal/db/tls_test.go new file mode 100644 index 0000000..4d7ebe4 --- /dev/null +++ b/apps/server/internal/db/tls_test.go @@ -0,0 +1,111 @@ +package db + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "math/big" + "os" + "path/filepath" + "testing" + "time" +) + +func TestEffectiveSSLMode(t *testing.T) { + if got := EffectiveSSLMode(ConnReq{}); got != "disable" { + t.Fatalf("empty -> disable, got %s", got) + } + if got := EffectiveSSLMode(ConnReq{SSL: true}); got != "require" { + t.Fatalf("ssl true -> require, got %s", got) + } + if got := EffectiveSSLMode(ConnReq{SSL: true, SSLMode: "skip-verify"}); got != "skip-verify" { + t.Fatalf("mode wins, got %s", got) + } + if got := EffectiveSSLMode(ConnReq{SSL: true, SSLMode: "disable"}); got != "disable" { + t.Fatalf("explicit disable, got %s", got) + } + if !SSLEnabled(ConnReq{SSLMode: "require"}) { + t.Fatal("require should enable") + } +} + +func TestTLSConfigFromDisable(t *testing.T) { + cfg, err := tlsConfigFrom(ConnReq{}) + if err != nil || cfg != nil { + t.Fatalf("disable => nil,nil got %#v %v", cfg, err) + } +} + +func TestTLSConfigFromSkipVerify(t *testing.T) { + cfg, err := tlsConfigFrom(ConnReq{SSL: true, SSLMode: "skip-verify", Host: "db.example"}) + if err != nil { + t.Fatal(err) + } + if cfg == nil || !cfg.InsecureSkipVerify { + t.Fatalf("expected InsecureSkipVerify, got %#v", cfg) + } + if cfg.ServerName != "db.example" { + t.Fatalf("ServerName %q", cfg.ServerName) + } +} + +func TestTLSConfigFromVerifyCARequiresPath(t *testing.T) { + _, err := tlsConfigFrom(ConnReq{SSLMode: "verify-ca"}) + if err == nil { + t.Fatal("expected error without CA") + } +} + +func TestTLSConfigFromVerifyCAWithPEM(t *testing.T) { + dir := t.TempDir() + caPath := filepath.Join(dir, "ca.pem") + if err := writeTestCA(caPath); err != nil { + t.Fatal(err) + } + cfg, err := tlsConfigFrom(ConnReq{SSLMode: "verify-ca", SSLCA: caPath, Host: "127.0.0.1"}) + if err != nil { + t.Fatal(err) + } + if cfg == nil || cfg.RootCAs == nil { + t.Fatal("expected RootCAs") + } + if cfg.VerifyPeerCertificate == nil { + t.Fatal("expected custom verify for verify-ca") + } +} + +func TestTLSConfigFromBadCAPath(t *testing.T) { + _, err := tlsConfigFrom(ConnReq{SSLMode: "require", SSLCA: "/no/such/ca.pem"}) + if err == nil { + t.Fatal("expected read error") + } +} + +func writeTestCA(path string) error { + key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return err + } + tmpl := &x509.Certificate{ + SerialNumber: big.NewInt(1), + Subject: pkix.Name{CommonName: "opsxdb-test-ca"}, + NotBefore: time.Now().Add(-time.Hour), + NotAfter: time.Now().Add(24 * time.Hour), + IsCA: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature, + BasicConstraintsValid: true, + } + der, err := x509.CreateCertificate(rand.Reader, tmpl, tmpl, &key.PublicKey, key) + if err != nil { + return err + } + f, err := os.Create(path) + if err != nil { + return err + } + defer f.Close() + return pem.Encode(f, &pem.Block{Type: "CERTIFICATE", Bytes: der}) +} diff --git a/apps/server/internal/db/types.go b/apps/server/internal/db/types.go new file mode 100644 index 0000000..74ac725 --- /dev/null +++ b/apps/server/internal/db/types.go @@ -0,0 +1,98 @@ +package db + +import "time" + +const DefaultRowLimit = 1000 +const MaxRowLimit = 10000 + +// ConnReq 连接 / 执行请求。sessionId 有值时服务端用会话里的账密,请求里的 password 可空。 +type ConnReq struct { + Driver string `json:"driver"` + Host string `json:"host"` + Port int `json:"port"` + User string `json:"user"` + Password string `json:"password"` + Database string `json:"database"` + SQL string `json:"sql"` + Service string `json:"service"` + SSL bool `json:"ssl"` + SSLMode string `json:"sslMode"` // disable|require|skip-verify|verify-ca + SSLCA string `json:"sslCA"` + SSLCert string `json:"sslCert"` + SSLKey string `json:"sslKey"` + SessionID string `json:"sessionId"` + Table string `json:"table"` + Limit int `json:"limit"` + Key string `json:"key"` // Redis key (get/delete) + Pattern string `json:"pattern"` // Redis SCAN match + + SSHEnabled bool `json:"sshEnabled"` + SSHHost string `json:"sshHost"` + SSHPort int `json:"sshPort"` + SSHUser string `json:"sshUser"` + SSHAuth string `json:"sshAuth"` // password | privateKey + SSHPassword string `json:"sshPassword"` + SSHPrivateKey string `json:"sshPrivateKey"` + SSHPassphrase string `json:"sshPassphrase"` +} + +type ColumnInfo struct { + Name string `json:"name"` + Type string `json:"type"` + Nullable bool `json:"nullable"` + Key string `json:"key,omitempty"` + Default any `json:"default,omitempty"` + Comment string `json:"comment,omitempty"` +} + +// ExecResult 与前端 QueryResult 字段对齐 +type ExecResult struct { + Columns []string `json:"columns"` + Rows []map[string]interface{} `json:"rows"` + RowCount int `json:"rowCount"` + TimeMs int64 `json:"timeMs"` + Error string `json:"error,omitempty"` + Truncated bool `json:"truncated,omitempty"` + Limit int `json:"limit,omitempty"` +} + +func normalize(v interface{}) interface{} { + switch t := v.(type) { + case nil: + return nil + case []byte: + return string(t) + case time.Time: + return t.Format(time.RFC3339Nano) + default: + return t + } +} + +// Redis key browse (dedicated endpoints; do not reshape SQL tables []). +type RedisKeyInfo struct { + Name string `json:"name"` + Type string `json:"type"` + TTL int64 `json:"ttl"` // seconds; -1 no expire; -2 missing +} + +type RedisKeysResult struct { + Keys []RedisKeyInfo `json:"keys"` + Truncated bool `json:"truncated,omitempty"` + TimeMs int64 `json:"timeMs"` + Error string `json:"error,omitempty"` +} + +// RedisKeyValue feeds ResultPane (columns/rows) plus type/ttl meta. +type RedisKeyValue struct { + Name string `json:"name"` + Type string `json:"type"` + TTL int64 `json:"ttl"` + Columns []string `json:"columns"` + Rows []map[string]interface{} `json:"rows"` + RowCount int `json:"rowCount"` + TimeMs int64 `json:"timeMs"` + Truncated bool `json:"truncated,omitempty"` + Limit int `json:"limit,omitempty"` + Error string `json:"error,omitempty"` +} diff --git a/apps/server/internal/docs/assets/docs.css b/apps/server/internal/docs/assets/docs.css new file mode 100644 index 0000000..9b85ea8 --- /dev/null +++ b/apps/server/internal/docs/assets/docs.css @@ -0,0 +1,39 @@ +:root { + --bg: #0e1116; --panel: #151a21; --border: #243044; + --text: #e6edf7; --muted: #8b9bb4; --accent: #3b82f6; +} +* { box-sizing: border-box; } +body { margin: 0; font: 13px/1.55 ui-sans-serif, system-ui, sans-serif; background: var(--bg); color: var(--text); } +.top { + height: 44px; border-bottom: 1px solid var(--border); background: var(--panel); + display: flex; align-items: center; justify-content: space-between; padding: 0 16px; +} +.brand { display: flex; align-items: center; gap: 8px; color: var(--text); text-decoration: none; font-weight: 600; } +.brand em { color: var(--muted); font-style: normal; font-weight: 500; } +.logo { + width: 24px; height: 24px; border-radius: 6px; background: var(--accent); + display: grid; place-items: center; font-size: 10px; font-weight: 700; color: #fff; +} +.top a { color: var(--muted); text-decoration: none; font-size: 12.5px; } +.layout { display: flex; min-height: calc(100vh - 44px); } +.side { + width: 200px; border-right: 1px solid var(--border); padding: 12px 10px; + background: var(--panel); display: flex; flex-direction: column; gap: 2px; +} +.nav-link { + display: block; padding: 6px 8px; border-radius: 6px; color: var(--muted); + text-decoration: none; font-size: 12.5px; +} +.nav-link:hover { color: var(--text); background: #1a2230; } +.nav-link.active { color: var(--text); background: color-mix(in srgb, var(--accent) 18%, transparent); } +.content { flex: 1; padding: 24px 28px; max-width: 720px; } +.content h1 { font-size: 20px; margin: 0 0 14px; } +.content h2 { font-size: 15px; margin: 22px 0 8px; } +.content p, .content li { color: #c5d0e0; } +.content code, .content pre { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; +} +.content pre { + background: var(--panel); border: 1px solid var(--border); border-radius: 8px; padding: 10px 12px; +} +.content code { background: #1a2230; padding: 1px 5px; border-radius: 4px; } diff --git a/apps/server/internal/docs/content/connect.html b/apps/server/internal/docs/content/connect.html new file mode 100644 index 0000000..70583b7 --- /dev/null +++ b/apps/server/internal/docs/content/connect.html @@ -0,0 +1,7 @@ +

连库入门

+

在连库页选择驱动,填写主机 / 端口 / 账号,测试通过后保存并打开。

+
    +
  • 密码仅存本机(本地加密存储)。
  • +
  • 会话由本地后端维护;后端未启动时顶部会提示,不会白屏。
  • +
  • 断线后可在底栏重连,恢复当前库上下文。
  • +
\ No newline at end of file diff --git a/apps/server/internal/docs/content/faq.html b/apps/server/internal/docs/content/faq.html new file mode 100644 index 0000000..12ba477 --- /dev/null +++ b/apps/server/internal/docs/content/faq.html @@ -0,0 +1,7 @@ +

常见问题

+

打开后提示本地后端未就绪?

+

确认应用已拉起 sidecar;或手动 ./run.sh server 后重试。默认只监听 127.0.0.1:8788。

+

浏览器也能用吗?

+

可以调试,但产品说明与对外沟通应以桌面版为准,不要暗示 Web 即产品。

+

官网 / 文档在哪?

+

本地后端提供:http://127.0.0.1:8788/(官网)、/docs(文档)。

\ No newline at end of file diff --git a/apps/server/internal/docs/content/install.html b/apps/server/internal/docs/content/install.html new file mode 100644 index 0000000..550a417 --- /dev/null +++ b/apps/server/internal/docs/content/install.html @@ -0,0 +1,13 @@ +

安装与启动

+

OpsxDB 正式形态是桌面 GUI(Tauri)。开发态浏览器 :5174 只用于调试,不是产品交付。

+

桌面版

+
    +
  1. 安装本机构建产物(macOS .dmg / .app,或 npm run build:desktop 产出)。
  2. +
  3. 双击启动:应用会拉起本地后端(默认 127.0.0.1:8788),进入工作台。
  4. +
  5. 无需手动执行 ./run.sh web。
  6. +
+

开发调试

+
./run.sh gui   # 或 ./run.sh web
+
+

开发壳地址:http://127.0.0.1:5174。

+

改后端(apps/server)后请执行 ./run.sh restart(或等价重启 Gin),避免 :8788 仍跑旧二进制。

diff --git a/apps/server/internal/docs/content/workbench.html b/apps/server/internal/docs/content/workbench.html new file mode 100644 index 0000000..c617bec --- /dev/null +++ b/apps/server/internal/docs/content/workbench.html @@ -0,0 +1,7 @@ +

工作台简介

+

深色密度布局:左侧连接与对象树,中间编辑器与多结果 Tab,可选 AI 侧栏。

+
    +
  • 多结果集:成功结果保留;失败不盖掉已有成功集。
  • +
  • 执行计划:有则展示,无则禁用。
  • +
  • 书签:紧凑列表,回填 SQL,本地持久化。
  • +
\ No newline at end of file diff --git a/apps/server/internal/docs/docs.go b/apps/server/internal/docs/docs.go new file mode 100644 index 0000000..ab90030 --- /dev/null +++ b/apps/server/internal/docs/docs.go @@ -0,0 +1,89 @@ +// Package docs:用户文档骨架(嵌入 HTML,侧栏+正文密度对齐工作台)。 +package docs + +import ( + "embed" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +//go:embed content assets template.html +var files embed.FS + +type page struct { + Slug string + Title string + Body string +} + +var order = []page{ + {Slug: "install", Title: "安装与启动"}, + {Slug: "connect", Title: "连库入门"}, + {Slug: "workbench", Title: "工作台简介"}, + {Slug: "faq", Title: "常见问题"}, +} + +type Handler struct { + tpl string +} + +func New() *Handler { + raw, err := files.ReadFile("template.html") + if err != nil { + panic("docs: template.html 缺失: " + err.Error()) + } + return &Handler{tpl: string(raw)} +} + +func (h *Handler) Register(r *gin.Engine) { + r.GET("/docs", func(c *gin.Context) { c.Redirect(http.StatusFound, "/docs/install") }) + r.GET("/docs/:slug", h.Page) + r.GET("/docs/assets/*file", h.Asset) +} + +func (h *Handler) Page(c *gin.Context) { + slug := c.Param("slug") + bodyBytes, err := files.ReadFile("content/" + slug + ".html") + if err != nil { + c.Status(http.StatusNotFound) + return + } + title := slug + nav := strings.Builder{} + for _, p := range order { + cls := "nav-link" + if p.Slug == slug { + cls += " active" + title = p.Title + } + nav.WriteString(`` + p.Title + ``) + } + html := h.tpl + html = strings.ReplaceAll(html, "{{TITLE}}", title) + html = strings.ReplaceAll(html, "{{NAV}}", nav.String()) + html = strings.ReplaceAll(html, "{{BODY}}", string(bodyBytes)) + c.Header("Content-Type", "text/html; charset=utf-8") + c.Header("Cache-Control", "no-cache") + _, _ = c.Writer.WriteString(html) +} + +func (h *Handler) Asset(c *gin.Context) { + name := strings.TrimPrefix(c.Param("file"), "/") + if strings.Contains(name, "..") { + c.Status(http.StatusBadRequest) + return + } + data, err := files.ReadFile("assets/" + name) + if err != nil { + c.Status(http.StatusNotFound) + return + } + ct := "application/octet-stream" + if strings.HasSuffix(name, ".css") { + ct = "text/css; charset=utf-8" + } + c.Header("Content-Type", ct) + c.Data(http.StatusOK, ct, data) +} diff --git a/apps/server/internal/docs/template.html b/apps/server/internal/docs/template.html new file mode 100644 index 0000000..9198619 --- /dev/null +++ b/apps/server/internal/docs/template.html @@ -0,0 +1,19 @@ + + + + + +{{TITLE}} · OpsxDB 文档 + + + +
+ OpsxDB 文档 + +
+
+ +
{{BODY}}
+
+ + diff --git a/apps/server/internal/proxy/guard.go b/apps/server/internal/proxy/guard.go new file mode 100644 index 0000000..fc2184e --- /dev/null +++ b/apps/server/internal/proxy/guard.go @@ -0,0 +1,36 @@ +package proxy + +import ( + "fmt" + "net" + "net/url" + "strings" +) + +var blockedHosts = map[string]struct{}{ + "169.254.169.254": {}, + "metadata.google.internal": {}, +} + +// ValidateURL applies MVP SSRF basics. Week 2 should add configurable CIDR +// deny-lists and DNS-rebinding protection before exposing this beyond localhost. +func ValidateURL(raw string) (*url.URL, error) { + u, err := url.Parse(raw) + if err != nil { + return nil, fmt.Errorf("invalid url: %w", err) + } + if u.Scheme != "http" && u.Scheme != "https" { + return nil, fmt.Errorf("only http/https schemes are allowed") + } + if u.Hostname() == "" { + return nil, fmt.Errorf("url host is required") + } + host := strings.ToLower(strings.TrimSuffix(u.Hostname(), ".")) + if _, blocked := blockedHosts[host]; blocked { + return nil, fmt.Errorf("blocked metadata host") + } + if ip := net.ParseIP(host); ip != nil && ip.Equal(net.ParseIP("169.254.169.254")) { + return nil, fmt.Errorf("blocked metadata address") + } + return u, nil +} diff --git a/apps/server/internal/proxy/guard_test.go b/apps/server/internal/proxy/guard_test.go new file mode 100644 index 0000000..2750500 --- /dev/null +++ b/apps/server/internal/proxy/guard_test.go @@ -0,0 +1,23 @@ +package proxy + +import "testing" + +func TestValidateURL(t *testing.T) { + for _, tc := range []struct { + raw string + ok bool + }{ + {"https://example.com/a", true}, + {"file:///etc/passwd", false}, + {"http://169.254.169.254/latest/meta-data", false}, + {"http://metadata.google.internal/computeMetadata/v1", false}, + } { + _, err := ValidateURL(tc.raw) + if tc.ok && err != nil { + t.Fatalf("%s: %v", tc.raw, err) + } + if !tc.ok && err == nil { + t.Fatalf("expected rejection: %s", tc.raw) + } + } +} diff --git a/apps/server/internal/proxy/handler.go b/apps/server/internal/proxy/handler.go new file mode 100644 index 0000000..8fa1a87 --- /dev/null +++ b/apps/server/internal/proxy/handler.go @@ -0,0 +1,111 @@ +package proxy + +import ( + "bytes" + "io" + "net/http" + "strings" + "time" + + "github.com/gin-gonic/gin" +) + +const maxResponseBytes = 10 << 20 // 10 MiB MVP safety cap + +type Handler struct { + BaseClient *http.Client +} + +func NewHandler() *Handler { + return &Handler{BaseClient: &http.Client{Timeout: 30 * time.Second}} +} + +func (h *Handler) ProxyHTTP(c *gin.Context) { + var spec HttpSpec + if err := c.ShouldBindJSON(&spec); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "invalid HttpSpec", "detail": err.Error()}) + return + } + + method := strings.ToUpper(spec.Method) + switch method { + case http.MethodGet, http.MethodPost, http.MethodPut, http.MethodPatch, http.MethodDelete: + default: + c.JSON(http.StatusBadRequest, gin.H{"error": "unsupported method"}) + return + } + + u, err := ValidateURL(spec.URL) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + query := u.Query() + for key, value := range spec.Query { + query.Set(key, value) + } + u.RawQuery = query.Encode() + + var body io.Reader + if spec.Body != nil && spec.Body.Mode != "none" && method != http.MethodGet { + body = bytes.NewBufferString(spec.Body.Raw) + } + + req, err := http.NewRequestWithContext(c.Request.Context(), method, u.String(), body) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": "cannot create request", "detail": err.Error()}) + return + } + for key, value := range spec.Headers { + // Hop-by-hop headers are managed by net/http. + if !isHopByHop(key) { + req.Header.Set(key, value) + } + } + if spec.Body != nil && spec.Body.Mode == "json" && req.Header.Get("Content-Type") == "" { + req.Header.Set("Content-Type", "application/json") + } + + client := h.BaseClient + if spec.TimeoutMS > 0 { + client = &http.Client{Timeout: time.Duration(spec.TimeoutMS) * time.Millisecond} + } + started := time.Now() + res, err := client.Do(req) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": "upstream request failed", "detail": err.Error()}) + return + } + defer res.Body.Close() + + payload, err := io.ReadAll(io.LimitReader(res.Body, maxResponseBytes+1)) + if err != nil { + c.JSON(http.StatusBadGateway, gin.H{"error": "cannot read upstream response", "detail": err.Error()}) + return + } + if len(payload) > maxResponseBytes { + c.JSON(http.StatusBadGateway, gin.H{"error": "upstream response exceeds 10 MiB limit"}) + return + } + + headers := make(map[string]string, len(res.Header)) + for key, values := range res.Header { + headers[strings.ToLower(key)] = strings.Join(values, ", ") + } + c.JSON(http.StatusOK, HttpResult{ + Status: res.StatusCode, + Headers: headers, + Body: string(payload), + TimeMS: time.Since(started).Milliseconds(), + Size: len(payload), + }) +} + +func isHopByHop(name string) bool { + switch strings.ToLower(name) { + case "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "te", "trailer", "transfer-encoding", "upgrade", "host", "content-length": + return true + default: + return false + } +} diff --git a/apps/server/internal/proxy/types.go b/apps/server/internal/proxy/types.go new file mode 100644 index 0000000..e945925 --- /dev/null +++ b/apps/server/internal/proxy/types.go @@ -0,0 +1,23 @@ +package proxy + +type BodySpec struct { + Mode string `json:"mode"` + Raw string `json:"raw,omitempty"` +} + +type HttpSpec struct { + Method string `json:"method" binding:"required"` + URL string `json:"url" binding:"required"` + Headers map[string]string `json:"headers"` + Query map[string]string `json:"query,omitempty"` + Body *BodySpec `json:"body,omitempty"` + TimeoutMS int `json:"timeoutMs,omitempty"` +} + +type HttpResult struct { + Status int `json:"status"` + Headers map[string]string `json:"headers"` + Body string `json:"body"` + TimeMS int64 `json:"timeMs"` + Size int `json:"size"` +} diff --git a/apps/server/internal/server/router.go b/apps/server/internal/server/router.go new file mode 100644 index 0000000..eb8d024 --- /dev/null +++ b/apps/server/internal/server/router.go @@ -0,0 +1,229 @@ +package server + +import ( + "net/http" + "strings" + + "github.com/gin-gonic/gin" + "opsxdb/server/internal/ai" + "opsxdb/server/internal/db" + "opsxdb/server/internal/docs" + "opsxdb/server/internal/site" +) + +func NewRouter() *gin.Engine { + r := gin.New() + _ = r.SetTrustedProxies(nil) + r.Use(gin.Logger(), gin.Recovery(), localhostCORS()) + + r.GET("/health", health) + r.GET("/api/health", health) + + docs.New().Register(r) + site.New().Register(r) + + api := r.Group("/api") + { + api.POST("/db/test-connection", func(c *gin.Context) { + req, err := bindConn(c, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"ok": false, "error": err.Error()}) + return + } + if err := db.TestConnection(req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"ok": false, "error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"ok": true, "message": "connected"}) + }) + + api.POST("/db/open", func(c *gin.Context) { + req, err := bindConn(c, false) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + id, err := db.OpenSession(req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"ok": true, "sessionId": id, "message": "connected"}) + }) + + api.POST("/db/close", func(c *gin.Context) { + var req db.ConnReq + _ = c.ShouldBindJSON(&req) + if req.SessionID != "" { + db.CloseSession(req.SessionID) + } + c.JSON(http.StatusOK, gin.H{"ok": true}) + }) + + api.POST("/db/databases", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + list, err := db.ListDatabases(req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"databases": list}) + }) + + api.POST("/db/tables", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + list, err := db.ListTables(req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"tables": list}) + }) + + api.POST("/db/columns", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + list, err := db.ListColumns(req) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, gin.H{"columns": list}) + }) + + api.POST("/db/exec", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + res := db.ExecSQL(c.Request.Context(), req) + c.JSON(http.StatusOK, res) + }) + + api.POST("/db/redis/keys", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if !db.IsRedisDriver(req.Driver) { + c.JSON(http.StatusBadRequest, gin.H{"error": "仅 Redis 可用"}) + return + } + res := db.ListRedisKeys(req) + if res.Error != "" { + c.JSON(http.StatusBadRequest, res) + return + } + c.JSON(http.StatusOK, res) + }) + + api.POST("/db/redis/key", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if !db.IsRedisDriver(req.Driver) { + c.JSON(http.StatusBadRequest, gin.H{"error": "仅 Redis 可用"}) + return + } + res := db.GetRedisKey(req) + if res.Error != "" && res.Type == "" { + c.JSON(http.StatusBadRequest, res) + return + } + c.JSON(http.StatusOK, res) + }) + + api.POST("/db/redis/key/delete", func(c *gin.Context) { + req, err := bindConn(c, true) + if err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + if !db.IsRedisDriver(req.Driver) { + c.JSON(http.StatusBadRequest, gin.H{"error": "仅 Redis 可用"}) + return + } + res := db.DeleteRedisKey(req) + if res.Error != "" { + c.JSON(http.StatusBadRequest, res) + return + } + c.JSON(http.StatusOK, res) + }) + + api.POST("/ai/chat", func(c *gin.Context) { + var req ai.ChatReq + if err := c.ShouldBindJSON(&req); err != nil { + c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + return + } + c.JSON(http.StatusOK, ai.Chat(req)) + }) + + api.POST("/ai/generate", func(c *gin.Context) { + var req ai.ChatReq + _ = c.ShouldBindJSON(&req) + req.Action = "generate" + c.JSON(http.StatusOK, ai.Chat(req)) + }) + } + + return r +} + +func bindConn(c *gin.Context, allowSession bool) (db.ConnReq, error) { + var req db.ConnReq + if err := c.ShouldBindJSON(&req); err != nil { + return req, err + } + if allowSession && req.SessionID != "" { + return db.ResolveConn(req) + } + return req, nil +} + +func health(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{"ok": true, "service": "opsxdb"}) +} + +func localhostCORS() gin.HandlerFunc { + allowed := map[string]struct{}{ + "http://localhost:5174": {}, + "http://127.0.0.1:5174": {}, + "http://localhost:5173": {}, + "http://127.0.0.1:5173": {}, + "http://localhost:1420": {}, + "http://127.0.0.1:1420": {}, + "tauri://localhost": {}, + "http://tauri.localhost": {}, + } + return func(c *gin.Context) { + origin := c.GetHeader("Origin") + if _, ok := allowed[strings.TrimSuffix(origin, "/")]; ok { + c.Header("Access-Control-Allow-Origin", origin) + c.Header("Vary", "Origin") + c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization") + c.Header("Access-Control-Allow-Methods", "GET, POST, OPTIONS") + } + if c.Request.Method == http.MethodOptions { + c.AbortWithStatus(http.StatusNoContent) + return + } + c.Next() + } +} diff --git a/apps/server/internal/site/assets/site.css b/apps/server/internal/site/assets/site.css new file mode 100644 index 0000000..16d74d5 --- /dev/null +++ b/apps/server/internal/site/assets/site.css @@ -0,0 +1,50 @@ +:root { + --bg: #0e1116; + --panel: #151a21; + --border: #243044; + --text: #e6edf7; + --muted: #8b9bb4; + --accent: #3b82f6; +} +* { box-sizing: border-box; } +body { + margin: 0; + font: 14px/1.55 ui-sans-serif, system-ui, -apple-system, sans-serif; + background: var(--bg); + color: var(--text); +} +.nav { border-bottom: 1px solid var(--border); background: var(--panel); } +.nav .inner { + max-width: 880px; margin: 0 auto; padding: 0 20px; height: 48px; + display: flex; align-items: center; justify-content: space-between; +} +.brand { display: flex; align-items: center; gap: 10px; color: var(--text); text-decoration: none; font-weight: 600; } +.logo { + width: 28px; height: 28px; border-radius: 7px; background: var(--accent); + display: grid; place-items: center; font-size: 11px; font-weight: 700; color: #fff; +} +.nav a { color: var(--muted); text-decoration: none; margin-left: 16px; font-size: 13px; } +.nav a:hover { color: var(--text); } +.hero { max-width: 880px; margin: 0 auto; padding: 56px 20px 72px; } +.eyebrow { color: var(--accent); font-size: 12px; letter-spacing: .04em; margin: 0 0 12px; } +h1 { font-size: 28px; line-height: 1.25; margin: 0 0 14px; font-weight: 650; } +.lead { color: var(--muted); max-width: 52ch; margin: 0 0 24px; } +.actions { display: flex; gap: 10px; flex-wrap: wrap; margin-bottom: 36px; } +.btn { + display: inline-flex; align-items: center; height: 36px; padding: 0 14px; + border-radius: 8px; text-decoration: none; font-size: 13px; font-weight: 550; + border: 1px solid var(--border); +} +.btn.primary { background: var(--accent); border-color: var(--accent); color: #fff; } +.btn.ghost { color: var(--text); background: transparent; } +.caps { list-style: none; padding: 0; margin: 0; display: grid; gap: 10px; } +.caps li { + background: var(--panel); border: 1px solid var(--border); border-radius: 10px; + padding: 12px 14px; display: grid; gap: 4px; +} +.caps strong { font-size: 13px; } +.caps span { color: var(--muted); font-size: 12.5px; } +.foot { + border-top: 1px solid var(--border); color: var(--muted); + text-align: center; padding: 18px; font-size: 12px; +} diff --git a/apps/server/internal/site/site.go b/apps/server/internal/site/site.go new file mode 100644 index 0000000..2b9b8d4 --- /dev/null +++ b/apps/server/internal/site/site.go @@ -0,0 +1,58 @@ +// Package site:产品官网落地页(嵌入二进制,深色密度跟工作台)。 +package site + +import ( + "embed" + "net/http" + "strings" + + "github.com/gin-gonic/gin" +) + +//go:embed template.html assets +var files embed.FS + +type Handler struct { + html string +} + +func New() *Handler { + raw, err := files.ReadFile("template.html") + if err != nil { + panic("site: template.html 缺失: " + err.Error()) + } + return &Handler{html: string(raw)} +} + +func (h *Handler) Register(r *gin.Engine) { + r.GET("/", h.Page) + r.GET("/home", h.Page) + r.GET("/index.html", h.Page) + r.GET("/site/assets/*file", h.Asset) +} + +func (h *Handler) Page(c *gin.Context) { + c.Header("Content-Type", "text/html; charset=utf-8") + c.Header("Cache-Control", "no-cache") + _, _ = c.Writer.WriteString(h.html) +} + +func (h *Handler) Asset(c *gin.Context) { + name := strings.TrimPrefix(c.Param("file"), "/") + if strings.Contains(name, "..") { + c.Status(http.StatusBadRequest) + return + } + data, err := files.ReadFile("assets/" + name) + if err != nil { + c.Status(http.StatusNotFound) + return + } + ct := "application/octet-stream" + if strings.HasSuffix(name, ".css") { + ct = "text/css; charset=utf-8" + } + c.Header("Content-Type", ct) + c.Header("Cache-Control", "public, max-age=3600") + c.Data(http.StatusOK, ct, data) +} diff --git a/apps/server/internal/site/template.html b/apps/server/internal/site/template.html new file mode 100644 index 0000000..9767bde --- /dev/null +++ b/apps/server/internal/site/template.html @@ -0,0 +1,36 @@ + + + + + +OpsxDB — 本地优先的桌面数据库工作台 + + + + + +
+

桌面 GUI · 本地优先

+

连库、写 SQL、看结果——都在本机完成

+

正式形态是可安装的桌面应用,本地后端随应用启动。浏览器里的 :5174 只是开发壳,不是产品。

+ +
    +
  • 多驱动连库MySQL / Postgres / SQLite 等,会话保活
  • +
  • 工作台密度结果集、对象树、书签,深色紧凑布局
  • +
  • 本机后端双击启动即带本地服务,不依赖手动起 Vite
  • +
+
+
© 2026 OpsxDB · 产品形态为桌面 GUI
+ + diff --git a/apps/web/index.html b/apps/web/index.html new file mode 100644 index 0000000..5d0b739 --- /dev/null +++ b/apps/web/index.html @@ -0,0 +1,14 @@ + + + + + + + + OpsxDB + + +
+ + + diff --git a/apps/web/package.json b/apps/web/package.json new file mode 100644 index 0000000..6c52170 --- /dev/null +++ b/apps/web/package.json @@ -0,0 +1,30 @@ +{ + "name": "@opsxdb/web", + "version": "0.1.0", + "private": true, + "type": "module", + "scripts": { + "dev": "vite --host 127.0.0.1 --port 5174", + "build": "tsc -b && vite build", + "preview": "vite preview --host 127.0.0.1 --port 5174" + }, + "dependencies": { + "@opsxdb/core": "*", + "@opsxdb/transport": "*", + "@opsxdb/ui": "*", + "@tauri-apps/api": "^2.5.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^22.20.3", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.2", + "vite": "^6.2.2" + } +} diff --git a/apps/web/postcss.config.js b/apps/web/postcss.config.js new file mode 100644 index 0000000..2aa7205 --- /dev/null +++ b/apps/web/postcss.config.js @@ -0,0 +1,6 @@ +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/apps/web/public/favicon.svg b/apps/web/public/favicon.svg new file mode 100644 index 0000000..8accdbc --- /dev/null +++ b/apps/web/public/favicon.svg @@ -0,0 +1,7 @@ + + + + + + diff --git a/apps/web/public/icons/databases/dameng.svg b/apps/web/public/icons/databases/dameng.svg new file mode 100644 index 0000000..5315156 --- /dev/null +++ b/apps/web/public/icons/databases/dameng.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/elasticsearch.svg b/apps/web/public/icons/databases/elasticsearch.svg new file mode 100644 index 0000000..0da0d70 --- /dev/null +++ b/apps/web/public/icons/databases/elasticsearch.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/gaussdb.svg b/apps/web/public/icons/databases/gaussdb.svg new file mode 100644 index 0000000..f115a85 --- /dev/null +++ b/apps/web/public/icons/databases/gaussdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/influxdb.svg b/apps/web/public/icons/databases/influxdb.svg new file mode 100644 index 0000000..a6ed507 --- /dev/null +++ b/apps/web/public/icons/databases/influxdb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/kingbase.svg b/apps/web/public/icons/databases/kingbase.svg new file mode 100644 index 0000000..9d7f076 --- /dev/null +++ b/apps/web/public/icons/databases/kingbase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/mariadb.svg b/apps/web/public/icons/databases/mariadb.svg new file mode 100644 index 0000000..33794b1 --- /dev/null +++ b/apps/web/public/icons/databases/mariadb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/mongodb.svg b/apps/web/public/icons/databases/mongodb.svg new file mode 100644 index 0000000..8660ced --- /dev/null +++ b/apps/web/public/icons/databases/mongodb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/mysql.svg b/apps/web/public/icons/databases/mysql.svg new file mode 100644 index 0000000..853c55f --- /dev/null +++ b/apps/web/public/icons/databases/mysql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/oceanbase.svg b/apps/web/public/icons/databases/oceanbase.svg new file mode 100644 index 0000000..2ace405 --- /dev/null +++ b/apps/web/public/icons/databases/oceanbase.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/opengauss.svg b/apps/web/public/icons/databases/opengauss.svg new file mode 100644 index 0000000..6f46971 --- /dev/null +++ b/apps/web/public/icons/databases/opengauss.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/oracle.svg b/apps/web/public/icons/databases/oracle.svg new file mode 100644 index 0000000..f5449b2 --- /dev/null +++ b/apps/web/public/icons/databases/oracle.svg @@ -0,0 +1 @@ +Oracle \ No newline at end of file diff --git a/apps/web/public/icons/databases/polardb.png b/apps/web/public/icons/databases/polardb.png new file mode 100644 index 0000000..197ba75 Binary files /dev/null and b/apps/web/public/icons/databases/polardb.png differ diff --git a/apps/web/public/icons/databases/postgresql.svg b/apps/web/public/icons/databases/postgresql.svg new file mode 100644 index 0000000..abacb8f --- /dev/null +++ b/apps/web/public/icons/databases/postgresql.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/redis.svg b/apps/web/public/icons/databases/redis.svg new file mode 100644 index 0000000..1981b34 --- /dev/null +++ b/apps/web/public/icons/databases/redis.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/sqlite.svg b/apps/web/public/icons/databases/sqlite.svg new file mode 100644 index 0000000..dde0558 --- /dev/null +++ b/apps/web/public/icons/databases/sqlite.svg @@ -0,0 +1,7 @@ + + SQLite + + + + + diff --git a/apps/web/public/icons/databases/sqlserver.svg b/apps/web/public/icons/databases/sqlserver.svg new file mode 100644 index 0000000..b23e83d --- /dev/null +++ b/apps/web/public/icons/databases/sqlserver.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/tdsql.svg b/apps/web/public/icons/databases/tdsql.svg new file mode 100644 index 0000000..65627ff --- /dev/null +++ b/apps/web/public/icons/databases/tdsql.svg @@ -0,0 +1,15 @@ + + + + + + + Tencent Cloud + + + + + + + + diff --git a/apps/web/public/icons/databases/tidb.svg b/apps/web/public/icons/databases/tidb.svg new file mode 100644 index 0000000..ee0b723 --- /dev/null +++ b/apps/web/public/icons/databases/tidb.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/databases/zookeeper.svg b/apps/web/public/icons/databases/zookeeper.svg new file mode 100644 index 0000000..a281bab --- /dev/null +++ b/apps/web/public/icons/databases/zookeeper.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/apps/web/public/icons/icon-512.png b/apps/web/public/icons/icon-512.png new file mode 100644 index 0000000..165a23b Binary files /dev/null and b/apps/web/public/icons/icon-512.png differ diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx new file mode 100644 index 0000000..71f95e6 --- /dev/null +++ b/apps/web/src/main.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import { OpsxWorkspace } from "@opsxdb/ui"; +import "./styles.css"; + +ReactDOM.createRoot(document.getElementById("root")!).render( + + + , +); diff --git a/apps/web/src/styles.css b/apps/web/src/styles.css new file mode 100644 index 0000000..b868a70 --- /dev/null +++ b/apps/web/src/styles.css @@ -0,0 +1,427 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + /* Design tokens v1 — TablePlus/DataGrip density */ + color-scheme: dark; + --ops-bg: #0e1116; + --ops-surface: #151a21; + --ops-surface-2: #1a212b; + --ops-border: #2a3340; + --ops-border-soft: #1f2733; + --ops-muted: #8b95a5; + --ops-text: #e8edf5; + --ops-accent: #3b82f6; + --ops-accent-strong: #2563eb; + --ops-row-alt: rgba(26, 33, 43, 0.65); + --ops-toolbar-h: 36px; + --ops-tab-h: 32px; + --ops-row-h: 28px; + --ops-statusbar-h: 24px; + --ops-sidebar-w: 260px; + --ops-font-xs: 11px; + --ops-font-sm: 12.5px; + --ops-font-md: 14px; + --ops-bg-active: color-mix(in srgb, var(--ops-accent) 16%, transparent); + --ops-danger: #ef4444; + --ops-warn: #f59e0b; + } + html.light { + color-scheme: light; + --ops-bg: #f1f5f9; + --ops-surface: #ffffff; + --ops-surface-2: #f8fafc; + --ops-border: #e2e8f0; + --ops-border-soft: #eef2f7; + --ops-muted: #64748b; + --ops-text: #1e293b; + --ops-accent: #0891b2; + --ops-accent-strong: #0e7490; + --ops-row-alt: #f8fafc; + --ops-danger: #dc2626; + --ops-warn: #d97706; + } + * { scrollbar-color: #334155 transparent; scrollbar-width: thin; } + html.light * { scrollbar-color: #cbd5e1 transparent; } + body { + @apply m-0 font-sans antialiased; + background: var(--ops-bg); + color: var(--ops-text); + } + button, input, select, textarea { font: inherit; } +} + +@layer components { + .ops-shell { + background: var(--ops-bg); + color: var(--ops-text); + } + + .ops-input { + @apply rounded border px-2 py-1 outline-none transition; + font-size: var(--ops-font-sm); + border-color: var(--ops-border); + background: var(--ops-surface-2); + color: var(--ops-text); + } + .ops-input::placeholder { color: var(--ops-muted); opacity: 0.7; } + .ops-input:focus { + border-color: var(--ops-accent); + box-shadow: 0 0 0 1px color-mix(in srgb, var(--ops-accent) 35%, transparent); + } + + .ops-btn-primary { + @apply inline-flex items-center justify-center rounded px-2.5 font-semibold text-white shadow-sm transition disabled:cursor-not-allowed disabled:opacity-50; + height: var(--ops-row-h); + font-size: var(--ops-font-sm); + background: var(--ops-accent-strong); + } + .ops-btn-primary:hover:not(:disabled) { background: var(--ops-accent); } + + .ops-btn-secondary { + @apply inline-flex items-center justify-center rounded border px-2 transition; + height: var(--ops-row-h); + font-size: var(--ops-font-sm); + border-color: var(--ops-border); + background: var(--ops-surface-2); + color: var(--ops-text); + } + .ops-btn-secondary:hover { + border-color: color-mix(in srgb, var(--ops-accent) 45%, var(--ops-border)); + background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface-2)); + } + + .ops-btn-ghost { + @apply inline-flex items-center justify-center rounded px-1.5 transition; + height: var(--ops-row-h); + font-size: var(--ops-font-sm); + color: var(--ops-muted); + } + .ops-btn-ghost:hover { + color: var(--ops-text); + background: color-mix(in srgb, var(--ops-accent) 10%, transparent); + } + + .ops-btn-ai { + @apply rounded border px-1.5 py-0.5 text-[11px] font-medium transition; + border-color: var(--ops-border); + background: var(--ops-surface-2); + color: var(--ops-text); + } + .ops-btn-ai:hover:not(:disabled) { + border-color: color-mix(in srgb, var(--ops-accent) 50%, var(--ops-border)); + color: var(--ops-accent); + } + .ops-btn-ai:disabled { opacity: 0.5; cursor: not-allowed; } + + .ops-tabbar { + @apply flex shrink-0 items-stretch gap-0.5 border-b px-1; + height: var(--ops-tab-h); + border-color: var(--ops-border); + background: var(--ops-surface-2); + } + .ops-empty { + @apply flex flex-1 flex-col items-center justify-center gap-3 px-6 text-center; + color: var(--ops-muted); + background: var(--ops-bg); + } + .ops-empty h2 { + @apply m-0 font-semibold; + font-size: var(--ops-font-md); + color: var(--ops-text); + } + .ops-empty p { + @apply m-0 max-w-sm; + font-size: var(--ops-font-sm); + } + .ops-conn-row { + @apply flex w-full items-center gap-3 border-b px-3 text-left transition; + min-height: 44px; + border-color: var(--ops-border-soft); + background: var(--ops-surface); + } + .ops-conn-row:hover { + background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface)); + } + .ops-section-card { + @apply space-y-2 rounded border p-3; + border-color: var(--ops-border); + background: var(--ops-surface); + } + .ops-section-title { + @apply mb-1 font-semibold; + font-size: var(--ops-font-xs); + color: var(--ops-muted); + letter-spacing: 0.04em; + text-transform: uppercase; + } + .ops-tab { + @apply border-b-2 border-transparent px-2.5 py-1.5 text-[12px] font-medium transition; + color: var(--ops-muted); + } + .ops-tab:hover { color: var(--ops-text); } + .ops-tab-active { + border-color: var(--ops-accent); + color: var(--ops-accent); + } + + .ops-code { + @apply font-mono text-[12.5px] leading-[1.45] outline-none; + background: #070b11; + color: #cbd5e1; + border-color: var(--ops-border); + } + html.light .ops-code { + background: #f8fafc; + color: #334155; + } + + .ops-panel { + background: var(--ops-surface); + border-color: var(--ops-border); + } + + .ops-chrome-header { + @apply flex shrink-0 items-center gap-1.5 border-b px-2.5; + height: var(--ops-toolbar-h); + border-color: var(--ops-border); + background: color-mix(in srgb, var(--ops-surface) 92%, #000); + } + html.light .ops-chrome-header { + background: var(--ops-surface); + } + + .ops-section-label { + @apply flex h-7 shrink-0 items-center px-2.5 text-[10px] font-semibold uppercase tracking-[0.06em]; + color: var(--ops-muted); + border-color: var(--ops-border); + background: var(--ops-surface-2); + } + + .ops-statusbar { + @apply flex shrink-0 items-center gap-2.5 border-t px-2.5; + font-size: var(--ops-font-xs); + height: var(--ops-statusbar-h); + border-color: var(--ops-border); + background: var(--ops-surface-2); + color: var(--ops-muted); + } + + .ops-tree-item { + @apply flex w-full items-center gap-1 truncate rounded px-1.5 text-left leading-tight transition; + min-height: var(--ops-row-h); + font-size: var(--ops-font-sm); + color: color-mix(in srgb, var(--ops-text) 78%, var(--ops-muted)); + } + .ops-tree-item:hover { + background: color-mix(in srgb, var(--ops-accent) 10%, transparent); + color: var(--ops-accent); + } + .ops-tree-item-active { + background: color-mix(in srgb, var(--ops-accent) 14%, transparent); + color: var(--ops-accent); + } + + .ops-grid { + @apply w-full border-collapse text-left text-[12px]; + } + .ops-grid th { + @apply sticky top-0 border-b px-1.5 py-[3px] font-semibold whitespace-nowrap; + border-color: var(--ops-border); + background: var(--ops-surface-2); + color: var(--ops-muted); + } + .ops-grid td { + @apply border-b px-1.5 py-[2px] font-mono whitespace-nowrap; + border-color: var(--ops-border-soft); + color: var(--ops-text); + } + .ops-grid tbody tr:nth-child(even) td { + background: var(--ops-row-alt); + } + .ops-grid tbody tr:hover td { + background: color-mix(in srgb, var(--ops-accent) 8%, transparent); + } + .ops-grid tbody tr.ops-row-selected td { + background: var(--ops-bg-active) !important; + } + .ops-grid th { + position: relative; + user-select: none; + } + .ops-grid th .ops-col-resize { + position: absolute; + right: 0; + top: 0; + width: 5px; + height: 100%; + cursor: col-resize; + } + .ops-grid th.ops-sortable { + cursor: pointer; + } + .ops-driver-row { + display: flex; + align-items: center; + gap: 10px; + width: 100%; + min-height: var(--ops-row-h); + padding: 0 10px; + border-bottom: 1px solid var(--ops-border-soft); + text-align: left; + background: transparent; + color: var(--ops-text); + font-size: var(--ops-font-sm); + transition: background 0.1s; + } + .ops-driver-row:hover { + background: var(--ops-bg-active); + } + .ops-driver-row .ops-driver-meta { + margin-left: auto; + font-size: var(--ops-font-xs); + color: var(--ops-muted); + } + .ops-tab-dirty::before { + content: "●"; + margin-right: 4px; + font-size: 8px; + color: var(--ops-accent); + } + .ops-grid .ops-null { + color: var(--ops-muted); + font-style: italic; + } + .ops-grid .ops-rownum { + @apply w-7 min-w-[1.75rem] text-right select-none; + color: var(--ops-muted); + background: var(--ops-surface-2) !important; + } + + .ops-modal { + @apply overflow-hidden rounded-lg border; + border-color: var(--ops-border); + background: var(--ops-surface); + color: var(--ops-text); + } + .ops-modal-header { + @apply border-b px-3.5 py-2 text-[13px] font-semibold; + border-color: var(--ops-border); + background: var(--ops-surface-2); + } + .ops-modal-body { + @apply px-3.5 py-2.5 text-[13px] leading-relaxed; + color: color-mix(in srgb, var(--ops-text) 90%, var(--ops-muted)); + } + .ops-modal-footer { + @apply flex justify-end gap-2 border-t px-3.5 py-2; + border-color: var(--ops-border); + } + + .ops-card { + @apply flex flex-col items-center gap-1.5 rounded-md border p-2.5 transition; + border-color: var(--ops-border); + background: color-mix(in srgb, var(--ops-surface-2) 80%, transparent); + } + .ops-card:hover { + border-color: color-mix(in srgb, var(--ops-accent) 55%, var(--ops-border)); + background: color-mix(in srgb, var(--ops-accent) 8%, var(--ops-surface-2)); + } + + details > summary .ops-tree-chevron { + display: inline-block; + transition: transform 0.12s ease; + } + details[open] > summary .ops-tree-chevron { + transform: rotate(90deg); + } +} + + + .ops-drop-line { + height: 2px; + margin: 1px 4px; + border-radius: 1px; + background: var(--ops-accent); + } + .ops-drop-slot { + border: 1px dashed var(--ops-border); + } + .ops-drop-slot-active { + border-color: var(--ops-accent); + background: color-mix(in srgb, var(--ops-accent) 12%, transparent); + } + + .ops-split-x { + width: 5px; + cursor: col-resize; + flex-shrink: 0; + background: transparent; + } + .ops-split-x:hover, + .ops-split-x:active { + background: color-mix(in srgb, var(--ops-accent) 45%, transparent); + } + .ops-split-y { + height: 5px; + cursor: row-resize; + flex-shrink: 0; + background: transparent; + } + .ops-split-y:hover, + .ops-split-y:active { + background: color-mix(in srgb, var(--ops-accent) 45%, transparent); + } + .cm-editor { + height: 100%; + outline: none; + background: transparent; + } + + .ops-close-faint { + display: inline-flex; + align-items: center; + justify-content: center; + width: 18px; + height: 18px; + border-radius: 3px; + font-size: 12px; + line-height: 1; + color: color-mix(in srgb, var(--ops-muted) 55%, transparent); + background: transparent; + } + .ops-close-faint:hover { + color: var(--ops-text); + background: color-mix(in srgb, var(--ops-muted) 18%, transparent); + } + .ops-ctx { + position: fixed; + z-index: 90; + min-width: 140px; + padding: 4px; + border-radius: 6px; + border: 1px solid var(--ops-border); + background: var(--ops-surface); + box-shadow: 0 8px 24px rgba(0,0,0,.35); + } + .ops-ctx button { + display: block; + width: 100%; + text-align: left; + padding: 6px 10px; + border-radius: 4px; + font-size: var(--ops-font-sm); + color: var(--ops-text); + background: transparent; + } + .ops-ctx button:hover { + background: var(--ops-bg-active); + } + .ops-tree-group { + font-size: 10px; + font-weight: 600; + letter-spacing: 0.04em; + color: var(--ops-muted); + } diff --git a/apps/web/src/styles.css.bak b/apps/web/src/styles.css.bak new file mode 100644 index 0000000..8cebfc3 --- /dev/null +++ b/apps/web/src/styles.css.bak @@ -0,0 +1,48 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { color-scheme: dark; } + html.light { color-scheme: light; } + * { scrollbar-color: #334155 transparent; scrollbar-width: thin; } + body { @apply m-0 bg-[#080d14] font-sans antialiased text-slate-200; } + html.light body { @apply bg-slate-50 text-slate-800; } + button, input, select, textarea { font: inherit; } +} + +@layer components { + .ops-input { + @apply rounded-md border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-200 outline-none transition placeholder:text-slate-600 focus:border-cyan-500 focus:ring-1 focus:ring-cyan-500/30; + } + html.light .ops-input { + @apply border-slate-300 bg-white text-slate-800 placeholder:text-slate-400; + } + .ops-btn-primary { + @apply rounded-md bg-cyan-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-cyan-500 disabled:cursor-not-allowed disabled:opacity-50; + } + .ops-btn-secondary { + @apply rounded-md border border-slate-700 bg-slate-900 px-3 py-1.5 text-sm text-slate-300 transition hover:border-slate-600 hover:bg-slate-800; + } + html.light .ops-btn-secondary { + @apply border-slate-300 bg-white text-slate-700 hover:bg-slate-100; + } + .ops-btn-ai { + @apply rounded-md border border-slate-700 bg-slate-900/80 px-2.5 py-1.5 text-xs font-medium text-slate-300 transition hover:border-cyan-600 hover:bg-cyan-950/40 hover:text-cyan-300; + } + .ops-tab { + @apply border-b-2 border-transparent px-4 py-3 text-xs font-medium text-slate-500 transition hover:text-slate-200; + } + .ops-tab-active { + @apply border-cyan-500 text-cyan-300; + } + .ops-code { + @apply rounded-md border border-slate-800 bg-[#070b11] p-3 font-mono text-xs leading-5 text-slate-300 outline-none focus:border-cyan-700; + } + .ops-panel { + @apply border-slate-800 bg-slate-950/80; + } + html.light .ops-panel { + @apply border-slate-200 bg-white; + } +} diff --git a/apps/web/tailwind.config.ts b/apps/web/tailwind.config.ts new file mode 100644 index 0000000..6250766 --- /dev/null +++ b/apps/web/tailwind.config.ts @@ -0,0 +1,15 @@ +import type { Config } from "tailwindcss"; + +export default { + content: [ + "./index.html", + "./src/**/*.{ts,tsx}", + "../../packages/ui/src/**/*.{ts,tsx}", + ], + theme: { + extend: { + fontFamily: { mono: ["JetBrains Mono", "SFMono-Regular", "Menlo", "monospace"] }, + }, + }, + plugins: [], +} satisfies Config; diff --git a/apps/web/tsconfig.app.json b/apps/web/tsconfig.app.json new file mode 100644 index 0000000..7a3d8ce --- /dev/null +++ b/apps/web/tsconfig.app.json @@ -0,0 +1,17 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "noEmit": true, + "allowImportingTsExtensions": true, + "types": [ + "vite/client" + ] + }, + "include": [ + "src", + "../../packages/ui/src", + "../../packages/core/src", + "../../packages/transport/src" + ] +} diff --git a/apps/web/tsconfig.json b/apps/web/tsconfig.json new file mode 100644 index 0000000..ea9d0cd --- /dev/null +++ b/apps/web/tsconfig.json @@ -0,0 +1,11 @@ +{ + "files": [], + "references": [ + { + "path": "./tsconfig.app.json" + }, + { + "path": "./tsconfig.node.json" + } + ] +} diff --git a/apps/web/tsconfig.node.json b/apps/web/tsconfig.node.json new file mode 100644 index 0000000..d8ff2ca --- /dev/null +++ b/apps/web/tsconfig.node.json @@ -0,0 +1,14 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "noEmit": true, + "composite": true, + "types": [ + "node" + ] + }, + "include": [ + "vite.config.ts" + ] +} diff --git a/apps/web/vite.config.ts b/apps/web/vite.config.ts new file mode 100644 index 0000000..a698be4 --- /dev/null +++ b/apps/web/vite.config.ts @@ -0,0 +1,29 @@ +import { defineConfig } from "vite"; +import react from "@vitejs/plugin-react"; +import path from "node:path"; + +export default defineConfig({ + plugins: [react()], + resolve: { + alias: { + "@opsxdb/core": path.resolve(__dirname, "../../packages/core/src/index.ts"), + "@opsxdb/transport": path.resolve(__dirname, "../../packages/transport/src/index.ts"), + "@opsxdb/ui": path.resolve(__dirname, "../../packages/ui/src/index.ts"), + }, + }, + server: { + host: "127.0.0.1", + port: 5174, + strictPort: true, + proxy: { + "/api": { + target: "http://127.0.0.1:8788", + changeOrigin: true, + }, + "/health": { + target: "http://127.0.0.1:8788", + changeOrigin: true, + }, + }, + }, +}); diff --git a/connection-types/dameng.yaml b/connection-types/dameng.yaml new file mode 100644 index 0000000..f342de1 --- /dev/null +++ b/connection-types/dameng.yaml @@ -0,0 +1,39 @@ +schemaVersion: 1 +order: 260 +dbType: dameng +rustVariant: Dameng +label: 达梦 DM8 +dialect: Dameng +runtimeMode: agent +mcpMode: bridge +agentKey: dameng +driverStoreVisible: true +driverStoreOrder: 1 +singleConnectionPool: true +metadataConnectionScoped: false +skipTcpProbe: true +defaultPort: 5236 +traits: + schemaAware: true + singleDatabase: true + clearableQuerySchema: true + fetchFirst: true + diagramSql: true +supportLevel: operate +capabilities: + queryExecution: true + metadataBrowse: true + objectBrowser: true + objectSource: true + schemaSearch: true + diagram: true + tableDataEdit: true + tableStructureEdit: true + tableImport: true + dataTransfer: true + sqlFileExecution: true + databaseCreate: false + fieldLineage: true + sqlExplain: true + userAdmin: false + driverManagement: true diff --git a/connection-types/mongodb.yaml b/connection-types/mongodb.yaml new file mode 100644 index 0000000..6c9e7c4 --- /dev/null +++ b/connection-types/mongodb.yaml @@ -0,0 +1,37 @@ +schemaVersion: 1 +order: 110 +dbType: mongodb +rustVariant: MongoDb +label: MongoDB (Legacy) +runtimeMode: agent +mcpMode: bridge +agentKey: mongodb +driverStoreVisible: true +driverStoreOrder: 41 +driverProfiles: + - profile: mongodb-legacy + agentKey: mongodb + label: MongoDB (Legacy) + storeVisible: false +singleConnectionPool: true +metadataConnectionScoped: false +skipTcpProbe: true +defaultPort: 27017 +supportLevel: connect +capabilities: + queryExecution: true + metadataBrowse: false + objectBrowser: true + objectSource: false + schemaSearch: false + diagram: false + tableDataEdit: false + tableStructureEdit: false + tableImport: false + dataTransfer: true + sqlFileExecution: false + databaseCreate: false + fieldLineage: false + sqlExplain: false + userAdmin: false + driverManagement: false diff --git a/connection-types/mysql.yaml b/connection-types/mysql.yaml new file mode 100644 index 0000000..c1e69f8 --- /dev/null +++ b/connection-types/mysql.yaml @@ -0,0 +1,32 @@ +schemaVersion: 1 +order: 10 +dbType: mysql +rustVariant: Mysql +label: MySQL +dialect: MySQL +runtimeMode: native +mcpMode: direct +singleConnectionPool: false +metadataConnectionScoped: true +skipTcpProbe: false +defaultPort: 3306 +traits: + diagramSql: true +supportLevel: operate +capabilities: + queryExecution: true + metadataBrowse: true + objectBrowser: true + objectSource: true + schemaSearch: true + diagram: true + tableDataEdit: true + tableStructureEdit: true + tableImport: true + dataTransfer: true + sqlFileExecution: true + databaseCreate: true + fieldLineage: true + sqlExplain: true + userAdmin: true + driverManagement: false diff --git a/connection-types/oracle.yaml b/connection-types/oracle.yaml new file mode 100644 index 0000000..45d4fc7 --- /dev/null +++ b/connection-types/oracle.yaml @@ -0,0 +1,48 @@ +schemaVersion: 1 +order: 130 +dbType: oracle +rustVariant: Oracle +label: Oracle +dialect: Oracle +runtimeMode: agent +mcpMode: bridge +agentKey: oracle +driverStoreVisible: true +driverStoreOrder: 18 +driverProfiles: + - profile: oracle-legacy + agentKey: oracle + label: Oracle + storeVisible: false + - profile: oracle-10g + agentKey: oracle + label: Oracle + storeVisible: false +singleConnectionPool: true +metadataConnectionScoped: false +skipTcpProbe: true +defaultPort: 1521 +traits: + schemaAware: true + singleDatabase: true + clearableQuerySchema: true + fetchFirst: true + diagramSql: true +supportLevel: operate +capabilities: + queryExecution: true + metadataBrowse: true + objectBrowser: true + objectSource: true + schemaSearch: true + diagram: true + tableDataEdit: true + tableStructureEdit: true + tableImport: true + dataTransfer: true + sqlFileExecution: true + databaseCreate: false + fieldLineage: true + sqlExplain: true + userAdmin: false + driverManagement: true diff --git a/connection-types/postgres.yaml b/connection-types/postgres.yaml new file mode 100644 index 0000000..c48cab3 --- /dev/null +++ b/connection-types/postgres.yaml @@ -0,0 +1,36 @@ +schemaVersion: 1 +order: 20 +dbType: postgres +rustVariant: Postgres +label: PostgreSQL +dialect: PostgreSQL +runtimeMode: native +mcpMode: direct +singleConnectionPool: false +metadataConnectionScoped: false +skipTcpProbe: false +defaultPort: 5432 +traits: + schemaAware: true + treeSchema: true + pgVacuum: true + pgLikeStructure: true + diagramSql: true +supportLevel: operate +capabilities: + queryExecution: true + metadataBrowse: true + objectBrowser: true + objectSource: true + schemaSearch: true + diagram: true + tableDataEdit: true + tableStructureEdit: true + tableImport: true + dataTransfer: true + sqlFileExecution: true + databaseCreate: true + fieldLineage: true + sqlExplain: true + userAdmin: true + driverManagement: false diff --git a/connection-types/redis.yaml b/connection-types/redis.yaml new file mode 100644 index 0000000..4a58e0b --- /dev/null +++ b/connection-types/redis.yaml @@ -0,0 +1,29 @@ +schemaVersion: 1 +order: 70 +dbType: redis +rustVariant: Redis +label: Redis +runtimeMode: native +mcpMode: bridge +singleConnectionPool: false +metadataConnectionScoped: false +skipTcpProbe: false +defaultPort: 6379 +supportLevel: connect +capabilities: + queryExecution: true + metadataBrowse: false + objectBrowser: false + objectSource: false + schemaSearch: false + diagram: false + tableDataEdit: false + tableStructureEdit: false + tableImport: false + dataTransfer: false + sqlFileExecution: false + databaseCreate: false + fieldLineage: false + sqlExplain: false + userAdmin: false + driverManagement: false diff --git a/connection-types/sqlserver.yaml b/connection-types/sqlserver.yaml new file mode 100644 index 0000000..cdc3ff0 --- /dev/null +++ b/connection-types/sqlserver.yaml @@ -0,0 +1,41 @@ +schemaVersion: 1 +order: 100 +dbType: sqlserver +rustVariant: SqlServer +label: SQL Server +dialect: SQL Server +runtimeMode: native +mcpMode: bridge +driverProfiles: + - profile: sqlserver-legacy + agentKey: sqlserver-legacy + label: SQL Server legacy compatibility component + storeVisible: true + storeOrder: 47 +singleConnectionPool: false +metadataConnectionScoped: false +skipTcpProbe: false +defaultPort: 1433 +traits: + schemaAware: true + databaseSchemaQualified: true + treeSchema: true + diagramSql: true +supportLevel: operate +capabilities: + queryExecution: true + metadataBrowse: true + objectBrowser: true + objectSource: true + schemaSearch: true + diagram: true + tableDataEdit: true + tableStructureEdit: true + tableImport: true + dataTransfer: true + sqlFileExecution: true + databaseCreate: true + fieldLineage: true + sqlExplain: true + userAdmin: false + driverManagement: false diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..92cad05 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3355 @@ +{ + "name": "opsxdb", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "opsxdb", + "workspaces": [ + "apps/web", + "packages/*" + ], + "engines": { + "node": ">=20" + } + }, + "apps/web": { + "name": "@opsxdb/web", + "version": "0.1.0", + "dependencies": { + "@opsxdb/core": "*", + "@opsxdb/transport": "*", + "@opsxdb/ui": "*", + "@tauri-apps/api": "^2.5.0", + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/node": "^22.20.3", + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "@vitejs/plugin-react": "^4.4.1", + "autoprefixer": "^10.4.21", + "postcss": "^8.5.3", + "tailwindcss": "^3.4.17", + "typescript": "^5.8.2", + "vite": "^6.2.2" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.3.0.tgz", + "integrity": "sha512-U4+70Pc5ZS9osnCBCE5Jha/ciHM+Yp+CNMNC/7HvYbNRk1Ldd+f7qO65W5qfhu/TCv+/ozljlXXe9Nj8419DMA==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.29.7", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", + "dev": true, + "dependencies": { + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-globals": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", + "dev": true, + "dependencies": { + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", + "dev": true, + "dependencies": { + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.29.7.tgz", + "integrity": "sha512-G7sHYigPY17oO5SYWnfD/0MTBwVR781S/JI643e/JhUYgVgWE/61SoW3NH9KWUKyKq5LVh3npif99Wkt6j86Jw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.29.7.tgz", + "integrity": "sha512-TL0hMc9xzy86VD31nUiwzd5otRAcyEPcsegCxolO0PvcXuH1v0kECe/UIznYFihpkvU5wg/jk4v0TTEFfm53fw==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.29.7.tgz", + "integrity": "sha512-06IyK09H3wi4cGbhDBwp5gUGo0IKtnYa8tyTiephirPCK6fbobVGiXMMI5zLQ4aKEYP3wZ3ArU44o+8KMrSG/Q==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@codemirror/autocomplete": { + "version": "6.20.3", + "resolved": "https://registry.npmjs.org/@codemirror/autocomplete/-/autocomplete-6.20.3.tgz", + "integrity": "sha512-tlosUqb+3BbxCxZdu4tKeRghPFC+QM7q4X5YhKV2eCmPG+1r2F3f4AaSz5sCrFqUtX4Jh20VFTKecl16MgiV9g==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.17.0", + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@codemirror/commands": { + "version": "6.11.1", + "resolved": "https://registry.npmjs.org/@codemirror/commands/-/commands-6.11.1.tgz", + "integrity": "sha512-O/4hG3SC1YwcmQ0d2UVNDs+AsaNWd1iHVxbTeEBuqH+6bExAiPK3iS/BvpY6rZGURALv4ZD3sIgcCmRvw3ehBg==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.7.0", + "@codemirror/view": "^6.27.0", + "@lezer/common": "^1.1.0" + } + }, + "node_modules/@codemirror/lang-sql": { + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/@codemirror/lang-sql/-/lang-sql-6.10.0.tgz", + "integrity": "sha512-6ayPkEd/yRw0XKBx5uAiToSgGECo/GY2NoJIHXIIQh1EVwLuKoU8BP/qK0qH5NLXAbtJRLuT73hx7P9X34iO4w==", + "license": "MIT", + "dependencies": { + "@codemirror/autocomplete": "^6.0.0", + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@lezer/common": "^1.2.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0" + } + }, + "node_modules/@codemirror/language": { + "version": "6.12.4", + "resolved": "https://registry.npmjs.org/@codemirror/language/-/language-6.12.4.tgz", + "integrity": "sha512-1q4PaT+o6PbgpkJt4Q8Fv5XJxTy4FUZ4MWETtyiDw3J0Pyr9E2vqcKL+k9wcvjNTIsauxvE7OfmWj3FRPHQ76A==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.23.0", + "@lezer/common": "^1.5.0", + "@lezer/highlight": "^1.0.0", + "@lezer/lr": "^1.0.0", + "style-mod": "^4.0.0" + } + }, + "node_modules/@codemirror/state": { + "version": "6.7.5", + "resolved": "https://registry.npmjs.org/@codemirror/state/-/state-6.7.5.tgz", + "integrity": "sha512-QjLbZmY1Au3JiRrDVYFLRD0BZ3SOKS9pR3yjIkd7u27YY8TFD9/Q9fhPnLV5l1mHFSo3hHU/N31vpwEJOx4owQ==", + "license": "MIT", + "dependencies": { + "@marijn/find-cluster-break": "^1.0.0" + } + }, + "node_modules/@codemirror/theme-one-dark": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@codemirror/theme-one-dark/-/theme-one-dark-6.1.3.tgz", + "integrity": "sha512-NzBdIvEJmx6fjeremiGp3t/okrLPYT0d9orIc7AFun8oZcRk58aejkqhv6spnz4MLAevrKNPMQYXEWMg4s+sKA==", + "license": "MIT", + "dependencies": { + "@codemirror/language": "^6.0.0", + "@codemirror/state": "^6.0.0", + "@codemirror/view": "^6.0.0", + "@lezer/highlight": "^1.0.0" + } + }, + "node_modules/@codemirror/view": { + "version": "6.43.12", + "resolved": "https://registry.npmjs.org/@codemirror/view/-/view-6.43.12.tgz", + "integrity": "sha512-Nv0vxQ19NAqvB/c2pFzjIzFlzzJl7jmdtNkwOwGbn0Ks9mFAzibvumz7cQem5cRsFA2cEw2fg+uHZGbcHupLQQ==", + "license": "MIT", + "dependencies": { + "@codemirror/state": "^6.7.0", + "crelt": "^1.0.6", + "style-mod": "^4.1.0", + "w3c-keyname": "^2.2.4" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@lezer/common": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@lezer/common/-/common-1.5.2.tgz", + "integrity": "sha512-sxQE460fPZyU3sdc8lafxiPwJHBzZRy/udNFynGQky1SePYBdhkBl1kOagA9uT3pxR8K09bOrmTUqA9wb/PjSQ==", + "license": "MIT" + }, + "node_modules/@lezer/highlight": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@lezer/highlight/-/highlight-1.2.3.tgz", + "integrity": "sha512-qXdH7UqTvGfdVBINrgKhDsVTJTxactNNxLk7+UMwZhU13lMHaOBlJe9Vqp907ya56Y3+ed2tlqzys7jDkTmW0g==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.3.0" + } + }, + "node_modules/@lezer/lr": { + "version": "1.4.10", + "resolved": "https://registry.npmjs.org/@lezer/lr/-/lr-1.4.10.tgz", + "integrity": "sha512-rnCpTIBafOx4mRp43xOxDJbFipJm/c0cia/V5TiGlhmMa+wsSdoGmUN3w5Bqrks/09Q/D4tNAmWaT8p6NRi77A==", + "license": "MIT", + "dependencies": { + "@lezer/common": "^1.0.0" + } + }, + "node_modules/@marijn/find-cluster-break": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.4.tgz", + "integrity": "sha512-Wy0V7+SGUjnF9/TkiM1hKVDPj7jKXduPNboMVtHTA8dySMURWqfg/JZ9E2Sq8JgSJmkl7k7Qe9FLeMSrSraWmQ==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@opsxdb/core": { + "resolved": "packages/core", + "link": true + }, + "node_modules/@opsxdb/transport": { + "resolved": "packages/transport", + "link": true + }, + "node_modules/@opsxdb/ui": { + "resolved": "packages/ui", + "link": true + }, + "node_modules/@opsxdb/web": { + "resolved": "apps/web", + "link": true + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "dev": true + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.3.tgz", + "integrity": "sha512-w3Jnvi1ocaVm/c7yVPpfB98XeSRBMyzp6njL5MVVbGyXjpmUkN+s6Hp4t0PqhGCCaI1ZHMKXt/w0lA1RCaLVcw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.3.tgz", + "integrity": "sha512-uI/ESiaIbbRYAEhzy8PCUWDp1hB0bjAqM06mW9flOoNO4Q8DQpeoREhBR5Hegfl+wpXiguyJv6XSPzEN7OxyHQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.3.tgz", + "integrity": "sha512-oxhrd1jmXLwWZ83eQYDXxuqRdkqkzrjR3JobKeuUyfdNZo11FuQIvqEOZhyIT7OBHxXoGslDDjN0cQcM6T0TqQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.3.tgz", + "integrity": "sha512-7/YiIMghVE8DrxKvNdorAaJVdriOFgOIpdStnPx8ppx5zfTwC3jBCSEAIzB7JD5404m65THl6H93UTTVUvypmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.3.tgz", + "integrity": "sha512-GXFZRRoMAytaI5z6N3Zhfw0WL18Q0M8r95D5hlC4GqE/lGk8pbSJNUBoOWDfbm6dTciqHj2nU87tI5f6XhQiOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.3.tgz", + "integrity": "sha512-77W+8X3ddYgPxUpB8nZFQs2Mq+wc4HVlcSRtApXLjYBcnPMkttrSnU8VwKQjeWYhMsITHFs5cWBQ8vz1Q+5RHQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.3.tgz", + "integrity": "sha512-FVkwK+iUC+mq+GipVK46rRVticfAPtvPUNlqlGXUDxdVk/UGjQiiiUVPUrEXdSpU2ufU0XxLGyTqDtBidDOVmg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.3.tgz", + "integrity": "sha512-+aGU1t3398yQOVj1Bz8o3e+KtswxAPvO+mtxtNdfXYMkXIHu7XhhkCD7/DEH9q8tF8uhDnMWvfpUKI8y1sZJsg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.3.tgz", + "integrity": "sha512-cR0kjpRXR2KJ2oQK8E2KTPtphs+b9hZ8IhTZubNryt/RsqgdOZBQ2Zq0q5UedtiIi0rs3jVhJh55RE1ZHUVGUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.3.tgz", + "integrity": "sha512-y1RYi4Q3/9ByVWSSt9kX2ustE0B7kFYbJ6zZdVZVyqopZs3yhCTwRfrjIX4vezUJInma/Gs6BOFDJg7yZmJ0IQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.3.tgz", + "integrity": "sha512-DNhEA5viIj3Z5bZLE4z4oV8N5ozWqDwyt7T6KG7VdLDJ0nW+rNOYlphBl4/3HQkK75qipPLsVOfStHHOwN9WSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.3.tgz", + "integrity": "sha512-17gQCqrIpXBX2Cmi9/TygnVOqGbzsba/iaqcYSL8FY7lNugg+7AiYNs5c5nKWD+NRQha36Sa0CqkJqH4XVHwnQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.3.tgz", + "integrity": "sha512-6LwVnZRIyINpdku/yOcI8Tm9YqLmhHK5emmlOOnW9tO0SYEm1FmKPcsSAGp0NBlqR2P04xaND4jvN6sTHqhq8A==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.3.tgz", + "integrity": "sha512-xMUqkTXlEUtI/p5AAukMwBRr1enU3efsTeF+bskeFfk8t1C9rcC8sLREcZXmTfAXEbvRdJVSonVJez3TMlbR3w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.3.tgz", + "integrity": "sha512-S3E94co9F9WRRqEaUoQZ38K1gCz6KiM+nL7/3ijq7fDGF3OznjS5TasgYITlvl27GQKtu4lOAOsr5MFwkijvOA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.3.tgz", + "integrity": "sha512-1QtRDwG42x5BJI3s9mxu5rEjDnfbSnk20HQ9/ylTAYnSwYwxMVb+Vgu34wzzTQ7ogqBybebgQNUDAvZVQ38DbA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.3.tgz", + "integrity": "sha512-BQhejF6ZXOpxbngiNTP12GCGQeaDVL2QXGeBVViKIYzFHM5RKxTxwUMB1fr1BeNFphFMpnRqC5QSXFSa4z6UQw==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.3.tgz", + "integrity": "sha512-SXagRwnI2Wlwlitllu59UK/nGVbD1CKPcNqDplHwIC4BqJcpXFjD32d1R/RbuISa95HdQrZM3/7v4bKiowFaLA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.3.tgz", + "integrity": "sha512-2IPozoEALRCziGqE8O9KMK60PMu5TS1huv4fwoeCexj+WjmcwFtX9CTOVbfXCUqcELAubEwRFPYlzb/WvwY2HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.3.tgz", + "integrity": "sha512-AoxqosUHT9IX54hFn2TiN6A7d6ZKTtE6pd2bqWtqkkNJ6HJGaU6FRouGX8L1O7R/ZwsnCnpQrHzb4pDEx+UHRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.3.tgz", + "integrity": "sha512-d+CaftKgmkFBzCwezMqqy1d0QNNYugqLCMcYVQWBy5SS2YfeMP8Q8ripkgx9O8IyBXXLHrJ+aaCV4U96usv6Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.3.tgz", + "integrity": "sha512-xXlDF6nR1eOuXbdDy5Hl5fmtY7teUDevF/k0O7IPoZe4Tpmdv+lgdE5JRsnhQtt37ql9P0VF2kAN9a0OCZdo+Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.3.tgz", + "integrity": "sha512-YtXAgLN+JP7Ay6qG3eWhc7IHMQPzLc8r3uvhAvlJIoCz/4Q32+Bl9Fmnywidh8v1GOIMmymjovfqY9ETAtysvA==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.3.tgz", + "integrity": "sha512-WuWtSJRNo549vzcfZyEgfqb6zeSgn1F+UE5kQ+BCjzz0W4MGCjntUHkZVc1VRuAM7+ULaSyhiPxD1spyewFvkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.3.tgz", + "integrity": "sha512-+lIKX7O0+IGe7WuhATaAMMeT7B76vfhXH/l9wLQL+nvyhbw2ohYCKIdWL56JfDu75CWt5oKRP4QFH/jkMtBquA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@tauri-apps/api": { + "version": "2.11.1", + "resolved": "https://registry.npmjs.org/@tauri-apps/api/-/api-2.11.1.tgz", + "integrity": "sha512-M2FPuYND2m+wh5hfW9ZpSdxMPdEJovPBWwoHJmwUpysTYNHaOkVFN419m/K0LIgjb/7KU2vBgsUepJWugQCvAA==", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/tauri" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", + "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dev": true, + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", + "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true + }, + "node_modules/@types/node": { + "version": "22.20.3", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.20.3.tgz", + "integrity": "sha512-DZmzkmwHzXrLPAXPyKNDzlIwMMUZCVacoD25ywdy5YTKGbOx/2ld+Q38Im2zJ0vBuZP5Prd3VZutKZyXwkOS8A==", + "dev": true, + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.3.0.tgz", + "integrity": "sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==", + "devOptional": true, + "dependencies": { + "csstype": "^3.2.2" + } + }, + "node_modules/@types/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-ZI7bU42mZXXKHn/qNLEw2IrbiINU7X5+vfgdixBHkCNpYWXjKgfQ/P+uyGb5CjOLB9UcnTeg3rylQtV2hym44Q==", + "dev": true, + "peerDependencies": { + "@types/react": "^19.3.0" + } + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "dev": true, + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" + }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/autoprefixer": { + "version": "10.6.1", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.6.1.tgz", + "integrity": "sha512-cL1Qz6ADZhcEbny/8HPfe99J6HhNoYtpX2LFLIbhgGE7Q1hlQVkYFdetDN7Id3KiQxhDrHwzlHr/YQCnZ8+xSA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "browserslist": "^4.28.9", + "caniuse-lite": "^1.0.30001810", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/baseline-browser-mapping": { + "version": "2.11.24", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.24.tgz", + "integrity": "sha512-hYrgxie335U08WqICoGqKRzV1HFXv6zdxwJE4ekCb80CM9a0SVVsN4QPwT67RraRo+9h8IATk6uxHJw7QSkdOg==", + "dev": true, + "bin": { + "baseline-browser-mapping": "dist/cli.cjs" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.29.0", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.29.0.tgz", + "integrity": "sha512-3GSvyjvDI4Dur1Meg2BekJquu5uF+9R9a1+5M1Mde192eZoXbeXjzgOsgqPS2V8D5wrrip0gR5Hf/GhWQ9ZzaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "baseline-browser-mapping": "^2.11.23", + "caniuse-lite": "^1.0.30001810", + "electron-to-chromium": "^1.5.427", + "node-releases": "^2.0.55", + "update-browserslist-db": "^1.3.3" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001810", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001810.tgz", + "integrity": "sha512-TITQPUkaz+aVk5GL6NhOdwk1aEaNTSDPsGFWrTuhKGtjTF70jL/Oht2W4c6rXUe5fu7Ie19VIahAXHIIiWWNeg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "engines": { + "node": ">= 16" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "dev": true + }, + "node_modules/crelt": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.7.tgz", + "integrity": "sha512-aK6BbWfhf4U/wCcLHKPJl/xa6VkVstRaPywWtMKGwuOLc/wZTyQYuoxgvZnNsBvv7Kg3YTBQYYBCggcviQczuA==", + "license": "MIT" + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "devOptional": true + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/discontinuous-range": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/discontinuous-range/-/discontinuous-range-1.0.0.tgz", + "integrity": "sha512-c68LpLbO+7kP/b1Hr1qs8/BJ09F5khZGTxqxZuhzxpmwJKOgRFHJWIb9/KmqnqHhLdO55aOxFH/EGBvUQbL/RQ==", + "license": "MIT" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.5.430", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.430.tgz", + "integrity": "sha512-e1QEj72Y4zd8RlNZVmoTg+iCOSVwpk05IOiiQwdrkwCSVlZfPthevErhE+nckGd2YbsXfp1SkisznhGVIXP2NQ==", + "dev": true + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "dependencies": { + "@types/estree": "^1.0.0" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.3", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.3.tgz", + "integrity": "sha512-XKv5nnLs6nLF71NgiKJLIZFLkPyIEuOselLG7ujZnGrRfQK8HpvY+WqKhAJUAdLomwVHErVS4LfxFlPq0/FTAw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/hasown": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.16.2", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz", + "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==", + "dev": true, + "dependencies": { + "hasown": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jiti": { + "version": "1.21.7", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", + "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", + "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/moo": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/moo/-/moo-0.5.3.tgz", + "integrity": "sha512-m2fmM2dDm7GZQsY7KK2cme8agi+AAljILjQnof7p1ZMDe6dQ4bdnSMx0cPppudoeNv5hEFQirN6u+O4fDE0IWA==", + "license": "BSD-3-Clause" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.19", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.19.tgz", + "integrity": "sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/nearley": { + "version": "2.20.1", + "resolved": "https://registry.npmjs.org/nearley/-/nearley-2.20.1.tgz", + "integrity": "sha512-+Mc8UaAebFzgV+KpI5n7DasuuQCHA89dmwm7JXw3TV43ukfNQ9DnBH3Mdb2g/I4Fdxc26pwimBWvjIw0UAILSQ==", + "license": "MIT", + "dependencies": { + "commander": "^2.19.0", + "moo": "^0.5.0", + "railroad-diagrams": "^1.0.0", + "randexp": "0.4.6" + }, + "bin": { + "nearley-railroad": "bin/nearley-railroad.js", + "nearley-test": "bin/nearley-test.js", + "nearley-unparse": "bin/nearley-unparse.js", + "nearleyc": "bin/nearleyc.js" + }, + "funding": { + "type": "individual", + "url": "https://nearley.js.org/#give-to-nearley" + } + }, + "node_modules/nearley/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "license": "MIT" + }, + "node_modules/node-releases": { + "version": "2.0.55", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.55.tgz", + "integrity": "sha512-mIrE/Cw9y+9Au6dS5vDKDhQza9YvG6w+ZrS6X+ZzA7yFW/soAeaups4Qzn1bL6g5FVy8WtP79+0j82oPIbqRjQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "engines": { + "node": ">= 14.16" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pirates": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz", + "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.5.28", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", + "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.18", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz", + "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz", + "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.1.1" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz", + "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "postcss-selector-parser": "^6.1.1" + }, + "engines": { + "node": ">=12.0" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.4.tgz", + "integrity": "sha512-bIoJLOmjCO1S9XdY/DcnR5hJxvrDir1PbGChrzXG3vw0/FOliy/fA3dmdhQ441kah4gKv+TwckGzex6wNS5cnQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/railroad-diagrams": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/railroad-diagrams/-/railroad-diagrams-1.0.0.tgz", + "integrity": "sha512-cz93DjNeLY0idrCNOH6PviZGRN9GJhsdm9hpn1YCS879fj4W+x5IFJhhkRZcwVgMmFF7R82UA/7Oh+R8lLZg6A==", + "license": "CC0-1.0" + }, + "node_modules/randexp": { + "version": "0.4.6", + "resolved": "https://registry.npmjs.org/randexp/-/randexp-0.4.6.tgz", + "integrity": "sha512-80WNmd9DA0tmZrw9qQa62GPPWfuXJknrmVmLcxvq4uZBdYqb1wYoKTmnlGUchvVWe0XiLupYkBoXVOxz3C8DYQ==", + "license": "MIT", + "dependencies": { + "discontinuous-range": "1.0.0", + "ret": "~0.1.10" + }, + "engines": { + "node": ">=0.12" + } + }, + "node_modules/react": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react/-/react-19.3.0.tgz", + "integrity": "sha512-E8LUcbtBWt20bbl2YoHfx4ZDBdxVTfOKtCZn9cDSJ4l6/nuoApcpIBcj47t2wZoVX8g2ZHuMHbiShgCR1T5Sog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/react-dom": { + "version": "19.3.0", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.3.0.tgz", + "integrity": "sha512-JDk8dgif51OjFoDE70+OT9ICyYr+69HlmihNwp1+Nsfbna3t5sIiCa9ZJktDmQ4/1b/rn26hIAR2uYXDMr5r0Q==", + "dependencies": { + "scheduler": "^0.28.0" + }, + "peerDependencies": { + "react": "^19.3.0" + } + }, + "node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.2.tgz", + "integrity": "sha512-/peqiBB/n07gQGLsWaHho3WfvUyRscw0gYTsEFMhrIe/nWLkYaf5SbKYjGYqtRV3aPwykJgF2VEMo1ac4bnsGA==", + "dev": true + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.12", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz", + "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==", + "dev": true, + "dependencies": { + "es-errors": "^1.3.0", + "is-core-module": "^2.16.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "license": "MIT", + "engines": { + "node": ">=0.12" + } + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.63.3", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.63.3.tgz", + "integrity": "sha512-1i2XreiAoMMXuPGD6Msj2xWrMMkHojNRKivInxGQcg7/1KuPuYlfUutLyh4drnOxUTHX9cHI4wFoat8D/NKaBw==", + "dev": true, + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.63.3", + "@rollup/rollup-android-arm64": "4.63.3", + "@rollup/rollup-darwin-arm64": "4.63.3", + "@rollup/rollup-darwin-x64": "4.63.3", + "@rollup/rollup-freebsd-arm64": "4.63.3", + "@rollup/rollup-freebsd-x64": "4.63.3", + "@rollup/rollup-linux-arm-gnueabihf": "4.63.3", + "@rollup/rollup-linux-arm-musleabihf": "4.63.3", + "@rollup/rollup-linux-arm64-gnu": "4.63.3", + "@rollup/rollup-linux-arm64-musl": "4.63.3", + "@rollup/rollup-linux-loong64-gnu": "4.63.3", + "@rollup/rollup-linux-loong64-musl": "4.63.3", + "@rollup/rollup-linux-ppc64-gnu": "4.63.3", + "@rollup/rollup-linux-ppc64-musl": "4.63.3", + "@rollup/rollup-linux-riscv64-gnu": "4.63.3", + "@rollup/rollup-linux-riscv64-musl": "4.63.3", + "@rollup/rollup-linux-s390x-gnu": "4.63.3", + "@rollup/rollup-linux-x64-gnu": "4.63.3", + "@rollup/rollup-linux-x64-musl": "4.63.3", + "@rollup/rollup-openbsd-x64": "4.63.3", + "@rollup/rollup-openharmony-arm64": "4.63.3", + "@rollup/rollup-win32-arm64-msvc": "4.63.3", + "@rollup/rollup-win32-ia32-msvc": "4.63.3", + "@rollup/rollup-win32-x64-gnu": "4.63.3", + "@rollup/rollup-win32-x64-msvc": "4.63.3", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/scheduler": { + "version": "0.28.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.28.0.tgz", + "integrity": "sha512-juorfCmIkIw8tT+p5BXSm6PJjQF/ycEYmKyzURCIt/RaZIhL+PulbQ9Yu2z1HdOJDdqDTlxA1+xKBmHXJsczAw==" + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sql-formatter": { + "version": "15.8.2", + "resolved": "https://registry.npmjs.org/sql-formatter/-/sql-formatter-15.8.2.tgz", + "integrity": "sha512-kTYRg5FIcvsDtYUG2Qn9pYT6xKwiLJN5TTIvc5Mur6hIg4pSfdpHu8Yyu5bqESLHnVM3mXzD446cb2+uEaKZXg==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "nearley": "^2.20.1" + }, + "bin": { + "sql-formatter": "bin/sql-formatter-cli.cjs" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true + }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/strip-literal/node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true + }, + "node_modules/style-mod": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/style-mod/-/style-mod-4.1.4.tgz", + "integrity": "sha512-XXWIQt633/EpAFx8aZDOTjBzrCaGmhvEQlQo6MVPfa2OzO2cWo+4hV9h+6UkHYlXGfy+ODXKUdP7Pthmcu5ATw==", + "license": "MIT" + }, + "node_modules/sucrase": { + "version": "3.35.1", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz", + "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "tinyglobby": "^0.2.11", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.19", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz", + "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.6.0", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.2", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.7", + "lilconfig": "^3.1.3", + "micromatch": "^4.0.8", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.1.1", + "postcss": "^8.4.47", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0", + "postcss-nested": "^6.2.0", + "postcss-selector-parser": "^6.1.2", + "resolve": "^1.22.8", + "sucrase": "^3.35.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.6.tgz", + "integrity": "sha512-u8KszXvGfU68hVcZpRHKG28T0krMuv2G5nDhiHaMLen/gIuFEgIJhaJuO69qjnXg5paSrbPMFfx3brNuN8eVSg==", + "dev": true, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "dev": true + }, + "node_modules/update-browserslist-db": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.3.3.tgz", + "integrity": "sha512-pJ2sYawQS0R/WI928Gj5GlPhTGzbMelq0+4INtSYNDV9ErKJcX6xjGWkoG/VnB3dpUm00zALaqkrUD77pO5TDQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vite": { + "version": "6.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.3.tgz", + "integrity": "sha512-NTKlcQjlAK7MlQoyb6LgaqHc8sso/pVyUJYWMws3jg21uTJw/LddqIFPcPqP6PzpgbIcZyKI85sFE4HBrQDA8A==", + "dev": true, + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/vite/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/zustand": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.15.tgz", + "integrity": "sha512-MpSEjRiBkA9crSYeOUH32rJC7SVqAbm0Fqcqge/bUi2PPoLcBWKOsG+C8mevmpr8TwXHBVkChbbJiyvkE+i/3A==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } + }, + "packages/core": { + "name": "@opsxdb/core", + "version": "0.1.0", + "devDependencies": { + "typescript": "^5.8.2", + "vitest": "^3.0.9" + } + }, + "packages/transport": { + "name": "@opsxdb/transport", + "version": "0.1.0", + "dependencies": { + "@opsxdb/core": "*" + }, + "devDependencies": { + "typescript": "^5.8.2" + }, + "peerDependencies": { + "@tauri-apps/api": "^2.0.0" + }, + "peerDependenciesMeta": { + "@tauri-apps/api": { + "optional": true + } + } + }, + "packages/ui": { + "name": "@opsxdb/ui", + "version": "0.1.0", + "dependencies": { + "@codemirror/commands": "^6.11.1", + "@codemirror/lang-sql": "^6.10.0", + "@codemirror/language": "^6.12.4", + "@codemirror/state": "^6.7.5", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.43.12", + "@opsxdb/core": "*", + "@opsxdb/transport": "*", + "sql-formatter": "^15.8.2", + "zustand": "^5.0.3" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "typescript": "^5.8.2" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..a877840 --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "opsxdb", + "private": true, + "workspaces": [ + "apps/web", + "packages/*" + ], + "scripts": { + "dev:web": "npm run dev -w @opsxdb/web", + "build:web": "npm run build -w @opsxdb/web", + "test:core": "npm run test -w @opsxdb/core", + "dev:desktop": "npm --prefix apps/desktop run tauri -- dev", + "build:desktop": "npm --prefix apps/desktop run tauri -- build" + }, + "engines": { + "node": ">=20" + } +} diff --git a/packages/core/package.json b/packages/core/package.json new file mode 100644 index 0000000..8f5adcf --- /dev/null +++ b/packages/core/package.json @@ -0,0 +1,19 @@ +{ + "name": "@opsxdb/core", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "test": "vitest run", + "test:watch": "vitest" + }, + "devDependencies": { + "typescript": "^5.8.2", + "vitest": "^3.0.9" + } +} diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts new file mode 100644 index 0000000..9849430 --- /dev/null +++ b/packages/core/src/index.ts @@ -0,0 +1,10 @@ +export type { + HttpMethod, + HttpSpec, + WsSpec, + HttpResult, + WsSession, + Transport, +} from "./types"; + +export { replaceVariables, replaceVariablesInSpec } from "./replaceVariables"; diff --git a/packages/core/src/replaceVariables.test.ts b/packages/core/src/replaceVariables.test.ts new file mode 100644 index 0000000..b27a932 --- /dev/null +++ b/packages/core/src/replaceVariables.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { replaceVariables, replaceVariablesInSpec } from "./replaceVariables"; + +describe("replaceVariables", () => { + it("replaces Postman-style {{var}}", () => { + expect(replaceVariables("{{baseUrl}}/api", { baseUrl: "https://x.test" })).toBe( + "https://x.test/api", + ); + }); + + it("leaves unknown vars intact", () => { + expect(replaceVariables("hello {{missing}}", {})).toBe("hello {{missing}}"); + }); + + it("supports multiple and spaced keys", () => { + expect( + replaceVariables("{{ host }}/{{path}}", { host: "h", path: "p" }), + ).toBe("h/p"); + }); + + it("replaceVariablesInSpec walks nested strings", () => { + const out = replaceVariablesInSpec( + { url: "{{baseUrl}}/v1", headers: { Authorization: "Bearer {{token}}" } }, + { baseUrl: "http://a", token: "t" }, + ); + expect(out.url).toBe("http://a/v1"); + expect(out.headers.Authorization).toBe("Bearer t"); + }); +}); diff --git a/packages/core/src/replaceVariables.ts b/packages/core/src/replaceVariables.ts new file mode 100644 index 0000000..4ce99f5 --- /dev/null +++ b/packages/core/src/replaceVariables.ts @@ -0,0 +1,35 @@ +/** + * Postman-style {{var}} replacement. + * Unknown variables are left as-is. + */ +export function replaceVariables( + input: string, + env: Record, +): string { + if (!input) return input; + return input.replace(/\{\{\s*([a-zA-Z0-9_.-]+)\s*\}\}/g, (match, key: string) => { + if (Object.prototype.hasOwnProperty.call(env, key)) { + return env[key] ?? ""; + } + return match; + }); +} + +export function replaceVariablesInSpec>( + spec: T, + env: Record, +): T { + const walk = (v: unknown): unknown => { + if (typeof v === "string") return replaceVariables(v, env); + if (Array.isArray(v)) return v.map(walk); + if (v && typeof v === "object") { + const out: Record = {}; + for (const [k, val] of Object.entries(v as Record)) { + out[k] = walk(val); + } + return out; + } + return v; + }; + return walk(spec) as T; +} diff --git a/packages/core/src/types.ts b/packages/core/src/types.ts new file mode 100644 index 0000000..e7f3ca9 --- /dev/null +++ b/packages/core/src/types.ts @@ -0,0 +1,39 @@ +export type HttpMethod = "GET" | "POST" | "PUT" | "PATCH" | "DELETE"; + +export type HttpSpec = { + method: HttpMethod; + url: string; + headers: Record; + query?: Record; + body?: { + mode: "none" | "json" | "raw" | "form" | "urlencoded"; + raw?: string; + }; + timeoutMs?: number; +}; + +export type WsSpec = { + url: string; + protocols?: string[]; + headers?: Record; +}; + +export type HttpResult = { + status: number; + headers: Record; + body: string; + timeMs: number; + size: number; +}; + +export interface WsSession { + onMessage(cb: (data: string) => void): void; + onStatus(cb: (s: string) => void): void; + send(data: string): void; + close(): void; +} + +export interface Transport { + http(spec: HttpSpec): Promise; + ws(spec: WsSpec): WsSession; +} diff --git a/packages/core/tsconfig.json b/packages/core/tsconfig.json new file mode 100644 index 0000000..a47cede --- /dev/null +++ b/packages/core/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src" + ] +} diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts new file mode 100644 index 0000000..f624398 --- /dev/null +++ b/packages/core/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from "vitest/config"; + +export default defineConfig({ + test: { + environment: "node", + }, +}); diff --git a/packages/transport/package.json b/packages/transport/package.json new file mode 100644 index 0000000..b50a84b --- /dev/null +++ b/packages/transport/package.json @@ -0,0 +1,25 @@ +{ + "name": "@opsxdb/transport", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "@opsxdb/core": "*" + }, + "devDependencies": { + "typescript": "^5.8.2" + }, + "peerDependencies": { + "@tauri-apps/api": "^2.0.0" + }, + "peerDependenciesMeta": { + "@tauri-apps/api": { + "optional": true + } + } +} diff --git a/packages/transport/src/browser.ts b/packages/transport/src/browser.ts new file mode 100644 index 0000000..2189ebd --- /dev/null +++ b/packages/transport/src/browser.ts @@ -0,0 +1,81 @@ +import type { HttpResult, HttpSpec, Transport, WsSession, WsSpec } from "@opsxdb/core"; + +function buildUrl(spec: HttpSpec): string { + const u = new URL(spec.url); + if (spec.query) { + for (const [k, v] of Object.entries(spec.query)) { + if (v !== undefined && v !== "") u.searchParams.set(k, v); + } + } + return u.toString(); +} + +function headersToRecord(h: Headers): Record { + const out: Record = {}; + h.forEach((v, k) => { + out[k] = v; + }); + return out; +} + +export const browserTransport: Transport = { + async http(spec: HttpSpec): Promise { + const started = performance.now(); + const init: RequestInit = { + method: spec.method, + headers: { ...spec.headers }, + signal: spec.timeoutMs + ? AbortSignal.timeout(spec.timeoutMs) + : undefined, + }; + if (spec.body && spec.body.mode !== "none" && spec.method !== "GET") { + init.body = spec.body.raw ?? ""; + if (spec.body.mode === "json" && !("content-type" in Object.fromEntries( + Object.entries(spec.headers).map(([k, v]) => [k.toLowerCase(), v]), + ))) { + (init.headers as Record)["Content-Type"] = "application/json"; + } + } + const res = await fetch(buildUrl(spec), init); + const body = await res.text(); + const timeMs = Math.round(performance.now() - started); + return { + status: res.status, + headers: headersToRecord(res.headers), + body, + timeMs, + size: new TextEncoder().encode(body).length, + }; + }, + + ws(spec: WsSpec): WsSession { + const socket = new WebSocket(spec.url, spec.protocols); + const messageCbs: Array<(data: string) => void> = []; + const statusCbs: Array<(s: string) => void> = []; + const emitStatus = (s: string) => statusCbs.forEach((cb) => cb(s)); + + emitStatus("connecting"); + socket.onopen = () => emitStatus("open"); + socket.onclose = () => emitStatus("closed"); + socket.onerror = () => emitStatus("error"); + socket.onmessage = (ev) => { + const data = typeof ev.data === "string" ? ev.data : String(ev.data); + messageCbs.forEach((cb) => cb(data)); + }; + + return { + onMessage(cb) { + messageCbs.push(cb); + }, + onStatus(cb) { + statusCbs.push(cb); + }, + send(data) { + socket.send(data); + }, + close() { + socket.close(); + }, + }; + }, +}; diff --git a/packages/transport/src/detect.ts b/packages/transport/src/detect.ts new file mode 100644 index 0000000..68e7be5 --- /dev/null +++ b/packages/transport/src/detect.ts @@ -0,0 +1,41 @@ +import type { Transport } from "@opsxdb/core"; +import { browserTransport } from "./browser"; +import { ginProxyTransport } from "./ginProxy"; +import { tauriNativeTransport } from "./tauriNative"; + +export type TransportKind = "tauri-native" | "gin-proxy" | "browser"; + +export type DetectOptions = { + ginBaseUrl?: string; + /** Force a transport regardless of environment */ + force?: TransportKind; +}; + +function isTauri(): boolean { + if (typeof window === "undefined") return false; + const w = window as unknown as { __TAURI_INTERNALS__?: unknown }; + return Boolean(w.__TAURI_INTERNALS__); +} + +export function detectTransport(opts: DetectOptions = {}): { + kind: TransportKind; + transport: Transport; +} { + if (opts.force === "browser") { + return { kind: "browser", transport: browserTransport }; + } + if (opts.force === "tauri-native") { + return { kind: "tauri-native", transport: tauriNativeTransport }; + } + if (opts.force === "gin-proxy") { + const base = opts.ginBaseUrl ?? "http://127.0.0.1:8788"; + return { kind: "gin-proxy", transport: ginProxyTransport(base) }; + } + + if (isTauri()) { + return { kind: "tauri-native", transport: tauriNativeTransport }; + } + + const base = opts.ginBaseUrl ?? "http://127.0.0.1:8788"; + return { kind: "gin-proxy", transport: ginProxyTransport(base) }; +} diff --git a/packages/transport/src/ginProxy.ts b/packages/transport/src/ginProxy.ts new file mode 100644 index 0000000..6ffb04c --- /dev/null +++ b/packages/transport/src/ginProxy.ts @@ -0,0 +1,35 @@ +import type { HttpResult, HttpSpec, Transport, WsSession, WsSpec } from "@opsxdb/core"; +import { browserTransport } from "./browser"; + +/** + * HTTP goes through Gin POST /v1/proxy/http. + * WS (MVP): falls back to browser WebSocket — custom headers require + * desktop/Gin tunnel later (Week 2). + */ +export function ginProxyTransport(baseUrl: string): Transport { + const root = baseUrl.replace(/\/$/, ""); + + return { + async http(spec: HttpSpec): Promise { + const res = await fetch(`${root}/v1/proxy/http`, { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify(spec), + }); + if (!res.ok) { + const text = await res.text(); + throw new Error(`gin-proxy HTTP ${res.status}: ${text}`); + } + return (await res.json()) as HttpResult; + }, + + ws(spec: WsSpec): WsSession { + // TODO(week2): custom headers need desktop/Gin WS tunnel. + // For MVP, fall back to browser WebSocket (no custom headers). + console.warn( + "[opsxdb/transport] gin-proxy WS falls back to browser WebSocket; custom headers need desktop/Gin tunnel later", + ); + return browserTransport.ws(spec); + }, + }; +} diff --git a/packages/transport/src/index.ts b/packages/transport/src/index.ts new file mode 100644 index 0000000..f1b2e22 --- /dev/null +++ b/packages/transport/src/index.ts @@ -0,0 +1,5 @@ +export { browserTransport } from "./browser"; +export { ginProxyTransport } from "./ginProxy"; +export { tauriNativeTransport } from "./tauriNative"; +export { detectTransport } from "./detect"; +export type { TransportKind, DetectOptions } from "./detect"; diff --git a/packages/transport/src/tauriNative.ts b/packages/transport/src/tauriNative.ts new file mode 100644 index 0000000..29a7744 --- /dev/null +++ b/packages/transport/src/tauriNative.ts @@ -0,0 +1,68 @@ +import type { HttpResult, HttpSpec, Transport, WsSession, WsSpec } from "@opsxdb/core"; + +async function invoke(cmd: string, args?: Record): Promise { + try { + const { invoke: tauriInvoke } = await import("@tauri-apps/api/core"); + return await tauriInvoke(cmd, args); + } catch (e) { + const msg = e instanceof Error ? e.message : String(e); + throw new Error( + `tauri-native invoke("${cmd}") failed (are you running inside OpsxDB desktop?): ${msg}`, + ); + } +} + +export const tauriNativeTransport: Transport = { + async http(spec: HttpSpec): Promise { + return invoke("http_send", { spec }); + }, + + ws(spec: WsSpec): WsSession { + const messageCbs: Array<(data: string) => void> = []; + const statusCbs: Array<(s: string) => void> = []; + let sessionId: string | null = null; + + const emitStatus = (s: string) => statusCbs.forEach((cb) => cb(s)); + + void (async () => { + try { + emitStatus("connecting"); + sessionId = await invoke("ws_connect", { spec }); + emitStatus("open"); + // Full event listen pipeline is Week 2; stub keeps UI usable. + } catch (e) { + emitStatus("error"); + messageCbs.forEach((cb) => + cb(`[ws_connect error] ${e instanceof Error ? e.message : String(e)}`), + ); + } + })(); + + return { + onMessage(cb) { + messageCbs.push(cb); + }, + onStatus(cb) { + statusCbs.push(cb); + }, + send(data) { + if (!sessionId) { + messageCbs.forEach((cb) => cb("[ws] not connected")); + return; + } + void invoke("ws_send", { sessionId, data }).catch((e) => { + messageCbs.forEach((cb) => + cb(`[ws_send error] ${e instanceof Error ? e.message : String(e)}`), + ); + }); + }, + close() { + if (!sessionId) return; + void invoke("ws_close", { sessionId }).finally(() => { + sessionId = null; + emitStatus("closed"); + }); + }, + }; + }, +}; diff --git a/packages/transport/tsconfig.json b/packages/transport/tsconfig.json new file mode 100644 index 0000000..a47cede --- /dev/null +++ b/packages/transport/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src" + ] +} diff --git a/packages/ui/package.json b/packages/ui/package.json new file mode 100644 index 0000000..7900fe2 --- /dev/null +++ b/packages/ui/package.json @@ -0,0 +1,32 @@ +{ + "name": "@opsxdb/ui", + "version": "0.1.0", + "private": true, + "type": "module", + "main": "./src/index.ts", + "types": "./src/index.ts", + "exports": { + ".": "./src/index.ts" + }, + "dependencies": { + "@codemirror/commands": "^6.11.1", + "@codemirror/lang-sql": "^6.10.0", + "@codemirror/language": "^6.12.4", + "@codemirror/state": "^6.7.5", + "@codemirror/theme-one-dark": "^6.1.3", + "@codemirror/view": "^6.43.12", + "@opsxdb/core": "*", + "@opsxdb/transport": "*", + "sql-formatter": "^15.8.2", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + }, + "devDependencies": { + "@types/react": "^19.0.10", + "@types/react-dom": "^19.0.4", + "typescript": "^5.8.2" + } +} diff --git a/packages/ui/src/AiPanel.tsx b/packages/ui/src/AiPanel.tsx new file mode 100644 index 0000000..44e2c21 --- /dev/null +++ b/packages/ui/src/AiPanel.tsx @@ -0,0 +1,185 @@ +import { callAi, type AiAction } from "./api"; +import { driverById } from "./catalog"; +import { useWorkspaceStore } from "./store"; + +const ACTIONS: { id: AiAction; label: string }[] = [ + { id: "generate", label: "生成" }, + { id: "explain", label: "解释" }, + { id: "optimize", label: "优化" }, + { id: "fix", label: "修复" }, + { id: "dialect", label: "方言" }, + { id: "sample", label: "样例" }, +]; + +export function AiPanel() { + const messages = useWorkspaceStore((s) => s.aiMessages); + const ask = useWorkspaceStore((s) => s.aiAsk); + const model = useWorkspaceStore((s) => s.aiModel); + const busy = useWorkspaceStore((s) => s.aiBusy); + const connections = useWorkspaceStore((s) => s.connections); + const activeConnectionId = useWorkspaceStore((s) => s.activeConnectionId); + const tabs = useWorkspaceStore((s) => s.tabs); + const activeTabId = useWorkspaceStore((s) => s.activeTabId); + const result = useWorkspaceStore((s) => s.result); + const set = useWorkspaceStore((s) => s.set); + const pushAi = useWorkspaceStore((s) => s.pushAi); + const updateActiveSql = useWorkspaceStore((s) => s.updateActiveSql); + + const conn = connections.find((c) => c.id === activeConnectionId); + const tab = tabs.find((t) => t.id === activeTabId); + const sql = tab?.sql ?? ""; + + const run = async (action: AiAction, prompt?: string) => { + const text = (prompt ?? ask).trim(); + if (action === "ask" && !text) return; + set("aiBusy", true); + if (action === "ask" || action === "generate") { + pushAi("user", text || ACTIONS.find((a) => a.id === action)?.label || action); + } else { + pushAi("user", `${ACTIONS.find((a) => a.id === action)?.label || action}`); + } + try { + const res = await callAi({ + action, + prompt: text, + sql, + driver: conn?.driver, + database: conn?.database || tab?.database, + error: result?.error, + }); + pushAi("assistant", res.content); + if (res.sql) updateActiveSql(res.sql); + if (action === "ask" || action === "generate") set("aiAsk", ""); + } catch (e) { + pushAi("assistant", `失败:${e instanceof Error ? e.message : String(e)}`); + } finally { + set("aiBusy", false); + } + }; + + return ( + + ); +} diff --git a/packages/ui/src/ConnectionForm.tsx b/packages/ui/src/ConnectionForm.tsx new file mode 100644 index 0000000..b90504e --- /dev/null +++ b/packages/ui/src/ConnectionForm.tsx @@ -0,0 +1,561 @@ +import { useState, type ReactNode } from "react"; +import { formKindOf, type DriverDef } from "./catalog"; +import type { SavedConnection } from "./store"; + +const COLORS = ["#3b82f6", "#22c55e", "#f59e0b", "#ef4444", "#8b5cf6", "#64748b"]; + +type TabId = "general" | "advanced" | "ssl" | "ssh"; + +const TABS: { id: TabId; label: string }[] = [ + { id: "general", label: "常规" }, + { id: "advanced", label: "高级" }, + { id: "ssl", label: "SSL" }, + { id: "ssh", label: "SSH" }, +]; + +function cx(...xs: Array) { + return xs.filter(Boolean).join(" "); +} + +function Row({ label, children }: { label: string; children: ReactNode }) { + return ( +
+
+ {label} +
+
{children}
+
+ ); +} + +function PasswordInput({ + value, + placeholder, + onChange, +}: { + value: string; + placeholder?: string; + onChange: (v: string) => void; +}) { + const [show, setShow] = useState(false); + return ( +
+ onChange(e.target.value)} + autoComplete="off" + /> + +
+ ); +} + +export type ConnectionFormProps = { + selected: DriverDef; + form: Partial; + setForm: (next: Partial) => void; + dsn: string; + setDsn: (v: string) => void; + onApplyDsn: () => void; +}; + +export function ConnectionForm({ selected, form, setForm, dsn, setDsn, onApplyDsn }: ConnectionFormProps) { + const [tab, setTab] = useState("general"); + const kind = formKindOf(selected.id); + const patch = (p: Partial) => setForm({ ...form, ...p }); + + const text = (key: keyof SavedConnection, ph = "") => ( + patch({ [key]: e.target.value } as Partial)} + /> + ); + + const portInput = ( + patch({ port: Number(e.target.value) || 0 })} + /> + ); + + const general = () => { + if (kind === "file") { + return ( + <> + {text("name", "我的数据库")} + + patch({ database: e.target.value })} + /> + + + ); + } + + if (kind === "redis") { + return ( + <> + {text("name", "Redis 连接")} + {text("host", selected.hints?.host ?? "127.0.0.1")} + {portInput} + + patch({ password: v })} + /> + + {text("user", "ACL ≥6 可选")} + + patch({ keySeparator: e.target.value || ":" })} + /> + + +
+ + + + + Sentinel / Cluster 本迭代未做 + +
+
+ + ); + } + + if (kind === "oracle") { + const idKind = form.oracleIdKind ?? "service"; + return ( + <> + {text("name", "Oracle 连接")} + + + + {text("host", selected.hints?.host ?? "127.0.0.1")} + {portInput} + +
+ + +
+
+ + patch({ service: e.target.value, database: e.target.value })} + /> + + + + + {text("user", selected.hints?.user ?? "system")} + + patch({ password: v })} /> + + + + + + ); + } + + // standard (mysql-like) + PG / SQL Server extras + const isPg = selected.id === "postgresql" || selected.id === "gaussdb" || selected.id === "kingbase"; + const isMssql = selected.id === "sqlserver"; + return ( + <> + {text("name", "我的数据库")} + {text("host", selected.hints?.host ?? "127.0.0.1")} + {portInput} + {text("user", selected.hints?.user ?? "root")} + + patch({ password: v })} /> + + + + + {(isPg || isMssql || selected.id === "mysql" || selected.mysqlCompat) && ( + + patch({ database: e.target.value })} + /> + + )} + {isMssql && ( + + + + )} + + ); + }; + + const advanced = () => ( + <> + + + + +
+ {COLORS.map((c) => ( +
+
+ {kind === "redis" && ( + + { + const v = e.target.value.trim(); + if (v === "") { + patch({ database: "" }); + return; + } + if (!/^\d{1,2}$/.test(v)) return; + const n = Number(v); + if (n >= 0 && n <= 15) patch({ database: v }); + }} + /> + + )} + {kind !== "file" && ( + +
+ setDsn(e.target.value)} + onKeyDown={(e) => { + if (e.key === "Enter") onApplyDsn(); + }} + /> + +
+
+ )} + + ); + + const sslOn = !!(form.ssl || (form.sslMode && form.sslMode !== "disable")); + const sslMode = (form.sslMode && form.sslMode !== "disable" ? form.sslMode : "require") as + | "require" + | "skip-verify" + | "verify-ca"; + + const ssl = () => ( + <> + + + + {kind === "file" ? ( +

+ 文件库(SQLite)不支持 SSL。 +

+ ) : sslOn ? ( + <> + + + + + patch({ sslCA: e.target.value })} + /> + + + patch({ sslCert: e.target.value })} + /> + + + patch({ sslKey: e.target.value })} + /> + +

+ {sslMode === "require" + ? "仅加密传输;路径可留空。verify-ca 需填 CA。" + : sslMode === "skip-verify" + ? "加密且不校验服务端证书(自签常用);路径可留空。" + : "校验服务端证书链;请填写 CA 证书路径。客户端证书/密钥可选。"} +

+ + ) : ( +

+ 开启后可选择 require / skip-verify / verify-ca,并可选填证书路径。 +

+ )} + + ); + + const ssh = () => ( + <> + + + + {form.sshEnabled && ( + <> + + patch({ sshHost: e.target.value })} + /> + + + patch({ sshPort: Number(e.target.value) || 22 })} + /> + + + patch({ sshUser: e.target.value })} + /> + + + + + {(form.sshAuth ?? "password") === "password" ? ( + + patch({ sshPassword: v })} + /> + + ) : ( + <> + +