update
This commit is contained in:
parent
8761b7cc23
commit
bfb1d31cb0
99
README.md
99
README.md
@ -3,10 +3,10 @@
|
||||
<div align="center">
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
**现代化多数据库管理工具**
|
||||
|
||||
@ -18,36 +18,35 @@
|
||||
|
||||
## ✨ 特性
|
||||
|
||||
- 🚀 **超轻量** - 基于 Tauri 2.0 + Rust,安装包仅 ~10MB
|
||||
- ⚡ **高性能** - Rust 原生数据库驱动,毫秒级响应
|
||||
- 🎨 **精美 UI** - Windows Metro 风格,深色主题
|
||||
- 🔌 **多数据库** - 支持 MySQL、PostgreSQL、SQLite、SQL Server 等
|
||||
- 🔐 **SSH 隧道** - 安全连接远程数据库
|
||||
- 📝 **智能编辑器** - SQL 语法高亮、智能补全、代码片段
|
||||
- 📊 **数据编辑** - 支持直接编辑表格数据
|
||||
- 📤 **导入导出** - 支持 JSON、Navicat NCX 格式
|
||||
- 🚀 **跨平台** - 基于 Electron,支持 Windows、macOS、Linux
|
||||
- ⚡ **高性能** - 原生数据库驱动,毫秒级响应
|
||||
- 🎨 **精美 UI** - Windows Metro 风格,深色主题,无边框窗口
|
||||
- 🔌 **多数据库** - 支持 MySQL、PostgreSQL、SQLite、SQL Server、MongoDB、Redis、MariaDB
|
||||
- 📝 **智能编辑器** - Monaco Editor,SQL 语法高亮、智能补全
|
||||
- 📊 **数据编辑** - 支持直接编辑表格数据,虚拟滚动大数据量
|
||||
- 🛠️ **表设计器** - Navicat 风格,可视化编辑字段、索引、外键、表选项
|
||||
- 🗃️ **完整管理** - 创建/删除/重命名/复制数据库和表
|
||||
- 📤 **导入导出** - 支持 JSON、Navicat NCX 格式连接配置导入导出
|
||||
- 🔄 **批量操作** - 支持多选连接批量删除管理
|
||||
|
||||
## 🗃️ 支持的数据库
|
||||
|
||||
| 数据库 | 状态 | 说明 |
|
||||
| 数据库 | 状态 | 驱动 |
|
||||
|--------|------|------|
|
||||
| 🐬 MySQL | ✅ | 完全支持 |
|
||||
| 🐘 PostgreSQL | ✅ | 完全支持 |
|
||||
| 💾 SQLite | ✅ | 完全支持 |
|
||||
| 📊 SQL Server | ✅ | 完全支持 |
|
||||
| 🦭 MariaDB | ✅ | 完全支持 |
|
||||
| 🍃 MongoDB | 🔜 | 开发中 |
|
||||
| ⚡ Redis | 🔜 | 开发中 |
|
||||
| 🔶 Oracle | 🔜 | 计划中 |
|
||||
| ❄️ Snowflake | 🔜 | 计划中 |
|
||||
| 🐬 MySQL | ✅ | mysql2 |
|
||||
| 🐘 PostgreSQL | ✅ | pg |
|
||||
| 💾 SQLite | ✅ | sql.js |
|
||||
| 📊 SQL Server | ✅ | mssql |
|
||||
| 🦭 MariaDB | ✅ | mysql2 |
|
||||
| 🍃 MongoDB | ✅ | mongodb |
|
||||
| ⚡ Redis | ✅ | ioredis |
|
||||
|
||||
## 🚀 快速开始
|
||||
|
||||
### 环境要求
|
||||
|
||||
- Node.js 18+
|
||||
- Rust (rustup)
|
||||
- [Tauri 依赖](https://tauri.app/v1/guides/getting-started/prerequisites)
|
||||
- npm 或 yarn
|
||||
|
||||
### 安装
|
||||
|
||||
@ -60,10 +59,10 @@ cd easysql
|
||||
npm install
|
||||
|
||||
# 开发模式运行
|
||||
npm run tauri:dev
|
||||
npm run electron:dev
|
||||
|
||||
# 构建应用
|
||||
npm run tauri:build
|
||||
npm run electron:build
|
||||
```
|
||||
|
||||
## 📸 界面预览
|
||||
@ -92,36 +91,35 @@ npm run tauri:build
|
||||
|
||||
## 🛠️ 技术栈
|
||||
|
||||
- **运行时**: Tauri 2.0 (Rust + WebView)
|
||||
- **后端**: Rust + SQLx + Tiberius
|
||||
- **运行时**: Electron 33
|
||||
- **前端**: React 18 + TypeScript 5
|
||||
- **样式**: Tailwind CSS 3
|
||||
- **构建**: Vite 5
|
||||
- **编辑器**: Monaco Editor
|
||||
- **数据库驱动**: mysql2, pg, sql.js, mssql, mongodb, ioredis
|
||||
|
||||
## 📁 项目结构
|
||||
|
||||
```
|
||||
easysql/
|
||||
├── src-tauri/ # Tauri/Rust 后端
|
||||
│ ├── src/
|
||||
│ │ ├── main.rs # 主程序入口
|
||||
│ │ ├── commands.rs # Tauri 命令
|
||||
│ │ ├── database.rs # 数据库连接管理
|
||||
│ │ ├── config.rs # 配置管理
|
||||
│ │ └── ssh.rs # SSH 隧道
|
||||
│ ├── Cargo.toml
|
||||
│ └── tauri.conf.json
|
||||
├── src/ # React 前端
|
||||
│ ├── components/ # UI 组件
|
||||
│ │ ├── TitleBar.tsx
|
||||
│ │ ├── Sidebar.tsx
|
||||
│ │ ├── MainContent.tsx
|
||||
│ │ ├── SqlEditor.tsx
|
||||
│ │ └── ConnectionModal.tsx
|
||||
├── electron/ # Electron 主进程
|
||||
│ ├── main.js # 主程序入口
|
||||
│ └── preload.js # 预加载脚本
|
||||
├── src/ # React 前端
|
||||
│ ├── components/ # UI 组件
|
||||
│ │ ├── TitleBar.tsx # 标题栏
|
||||
│ │ ├── Sidebar.tsx # 侧边栏(连接/数据库/表树)
|
||||
│ │ ├── MainContent.tsx # 主内容区
|
||||
│ │ ├── SqlEditor.tsx # SQL 编辑器(Monaco)
|
||||
│ │ ├── VirtualDataTable.tsx # 虚拟滚动数据表格
|
||||
│ │ ├── TableDesigner.tsx # 表设计器(Navicat 风格)
|
||||
│ │ ├── ConnectionModal.tsx # 连接配置弹窗
|
||||
│ │ ├── CreateDatabaseModal.tsx # 新建数据库弹窗
|
||||
│ │ ├── CreateTableModal.tsx # 快速新建表弹窗
|
||||
│ │ └── InputDialog.tsx # 通用输入对话框
|
||||
│ ├── lib/
|
||||
│ │ ├── tauri-api.ts # Tauri API 封装
|
||||
│ │ └── hooks.ts # 自定义 Hooks
|
||||
│ │ ├── electron-api.ts # Electron API 封装
|
||||
│ │ └── hooks.ts # 自定义 Hooks
|
||||
│ ├── App.tsx
|
||||
│ ├── types.ts
|
||||
│ └── index.css
|
||||
@ -143,6 +141,8 @@ easysql/
|
||||
| `Ctrl+Q` | 新建查询 |
|
||||
| `Ctrl+W` | 关闭当前标签 |
|
||||
| `Ctrl+F` | 搜索(侧边栏/表格) |
|
||||
| `双击连接` | 快速连接数据库 |
|
||||
| `右键菜单` | 连接/数据库/表操作 |
|
||||
|
||||
## 🔧 配置说明
|
||||
|
||||
@ -151,6 +151,15 @@ easysql/
|
||||
- macOS: `~/Library/Application Support/easysql/connections.json`
|
||||
- Linux: `~/.config/easysql/connections.json`
|
||||
|
||||
## 📦 npm 脚本
|
||||
|
||||
| 命令 | 说明 |
|
||||
|------|------|
|
||||
| `npm run dev` | 启动 Vite 开发服务器 |
|
||||
| `npm run build` | 构建前端资源 |
|
||||
| `npm run electron:dev` | 开发模式运行 Electron |
|
||||
| `npm run electron:build` | 打包 Electron 应用 |
|
||||
|
||||
## 🤝 贡献
|
||||
|
||||
欢迎提交 Issue 和 Pull Request!
|
||||
@ -162,5 +171,5 @@ MIT
|
||||
---
|
||||
|
||||
<div align="center">
|
||||
Made with ❤️ using Tauri + React + Rust
|
||||
Made with ❤️ using Electron + React + Node.js
|
||||
</div>
|
||||
|
||||
35
app-icon.svg
Normal file
35
app-icon.svg
Normal file
@ -0,0 +1,35 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1024" height="1024" viewBox="0 0 1024 1024">
|
||||
<defs>
|
||||
<linearGradient id="bg" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#6366f1"/>
|
||||
<stop offset="100%" style="stop-color:#4f46e5"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="db" x1="0%" y1="0%" x2="0%" y2="100%">
|
||||
<stop offset="0%" style="stop-color:#ffffff"/>
|
||||
<stop offset="100%" style="stop-color:#e0e7ff"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<!-- Background -->
|
||||
<rect width="1024" height="1024" rx="180" fill="url(#bg)"/>
|
||||
|
||||
<!-- Database Icon -->
|
||||
<g transform="translate(512, 512)">
|
||||
<!-- Database cylinder -->
|
||||
<ellipse cx="0" cy="-200" rx="220" ry="70" fill="url(#db)" stroke="#c7d2fe" stroke-width="8"/>
|
||||
<rect x="-220" y="-200" width="440" height="400" fill="url(#db)"/>
|
||||
<ellipse cx="0" cy="200" rx="220" ry="70" fill="url(#db)" stroke="#c7d2fe" stroke-width="8"/>
|
||||
|
||||
<!-- Middle lines -->
|
||||
<ellipse cx="0" cy="-50" rx="220" ry="70" fill="none" stroke="#a5b4fc" stroke-width="6"/>
|
||||
<ellipse cx="0" cy="100" rx="220" ry="70" fill="none" stroke="#a5b4fc" stroke-width="6"/>
|
||||
|
||||
<!-- Side borders -->
|
||||
<line x1="-220" y1="-200" x2="-220" y2="200" stroke="#c7d2fe" stroke-width="8"/>
|
||||
<line x1="220" y1="-200" x2="220" y2="200" stroke="#c7d2fe" stroke-width="8"/>
|
||||
|
||||
<!-- SQL text -->
|
||||
<text x="0" y="350" text-anchor="middle" font-family="Arial, sans-serif" font-size="120" font-weight="bold" fill="#ffffff">SQL</text>
|
||||
</g>
|
||||
</svg>
|
||||
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
1981
electron/main.js
Normal file
1981
electron/main.js
Normal file
File diff suppressed because it is too large
Load Diff
74
electron/preload.js
Normal file
74
electron/preload.js
Normal file
@ -0,0 +1,74 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
// 暴露安全的 API 给渲染进程
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
// 窗口控制
|
||||
minimize: () => ipcRenderer.invoke('window:minimize'),
|
||||
maximize: () => ipcRenderer.invoke('window:maximize'),
|
||||
close: () => ipcRenderer.invoke('window:close'),
|
||||
|
||||
// 配置存储
|
||||
loadConnections: () => ipcRenderer.invoke('config:load'),
|
||||
saveConnections: (connections) => ipcRenderer.invoke('config:save', connections),
|
||||
|
||||
// 数据库操作
|
||||
testConnection: (config) => ipcRenderer.invoke('db:test', config),
|
||||
connect: (config) => ipcRenderer.invoke('db:connect', config),
|
||||
disconnect: (id) => ipcRenderer.invoke('db:disconnect', id),
|
||||
query: (id, sql) => ipcRenderer.invoke('db:query', id, sql),
|
||||
getDatabases: (id) => ipcRenderer.invoke('db:getDatabases', id),
|
||||
getTables: (id, database) => ipcRenderer.invoke('db:getTables', id, database),
|
||||
getColumns: (id, database, table) => ipcRenderer.invoke('db:getColumns', id, database, table),
|
||||
getTableData: (id, database, table, page, pageSize) =>
|
||||
ipcRenderer.invoke('db:getTableData', id, database, table, page, pageSize),
|
||||
updateRow: (id, database, table, primaryKey, updates) =>
|
||||
ipcRenderer.invoke('db:updateRow', id, database, table, primaryKey, updates),
|
||||
deleteRow: (id, database, table, primaryKey) =>
|
||||
ipcRenderer.invoke('db:deleteRow', id, database, table, primaryKey),
|
||||
|
||||
// 数据库管理
|
||||
createDatabase: (id, dbName, charset, collation) =>
|
||||
ipcRenderer.invoke('db:createDatabase', id, dbName, charset, collation),
|
||||
dropDatabase: (id, dbName) =>
|
||||
ipcRenderer.invoke('db:dropDatabase', id, dbName),
|
||||
|
||||
// 表管理
|
||||
createTable: (id, database, tableName, columns) =>
|
||||
ipcRenderer.invoke('db:createTable', id, database, tableName, columns),
|
||||
dropTable: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:dropTable', id, database, tableName),
|
||||
truncateTable: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:truncateTable', id, database, tableName),
|
||||
renameTable: (id, database, oldName, newName) =>
|
||||
ipcRenderer.invoke('db:renameTable', id, database, oldName, newName),
|
||||
duplicateTable: (id, database, sourceTable, newTable, withData) =>
|
||||
ipcRenderer.invoke('db:duplicateTable', id, database, sourceTable, newTable, withData),
|
||||
|
||||
// 列管理
|
||||
addColumn: (id, database, tableName, column) =>
|
||||
ipcRenderer.invoke('db:addColumn', id, database, tableName, column),
|
||||
modifyColumn: (id, database, tableName, oldName, column) =>
|
||||
ipcRenderer.invoke('db:modifyColumn', id, database, tableName, oldName, column),
|
||||
dropColumn: (id, database, tableName, columnName) =>
|
||||
ipcRenderer.invoke('db:dropColumn', id, database, tableName, columnName),
|
||||
|
||||
// 表设计器相关
|
||||
getTableInfo: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:getTableInfo', id, database, tableName),
|
||||
getIndexes: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:getIndexes', id, database, tableName),
|
||||
getForeignKeys: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:getForeignKeys', id, database, tableName),
|
||||
getColumnNames: (id, database, tableName) =>
|
||||
ipcRenderer.invoke('db:getColumnNames', id, database, tableName),
|
||||
executeMultiSQL: (id, sqls) =>
|
||||
ipcRenderer.invoke('db:executeMultiSQL', id, sqls),
|
||||
|
||||
// 文件操作
|
||||
openFile: () => ipcRenderer.invoke('file:open'),
|
||||
saveFile: (filePath, content) => ipcRenderer.invoke('file:save', filePath, content),
|
||||
selectFile: (extensions) => ipcRenderer.invoke('file:select', extensions),
|
||||
saveDialog: (options) => ipcRenderer.invoke('file:saveDialog', options),
|
||||
writeFile: (filePath, content) => ipcRenderer.invoke('file:write', filePath, content),
|
||||
readFile: (filePath) => ipcRenderer.invoke('file:read', filePath)
|
||||
})
|
||||
5573
package-lock.json
generated
5573
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
48
package.json
48
package.json
@ -2,33 +2,65 @@
|
||||
"name": "easysql",
|
||||
"version": "1.0.0",
|
||||
"description": "Modern Database Management Tool",
|
||||
"main": "electron/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"preview": "vite preview",
|
||||
"tauri": "tauri",
|
||||
"tauri:dev": "tauri dev",
|
||||
"tauri:build": "tauri build"
|
||||
"electron:dev": "concurrently \"vite\" \"wait-on http://localhost:5173 && electron .\"",
|
||||
"electron:build": "vite build && electron-builder"
|
||||
},
|
||||
"dependencies": {
|
||||
"@monaco-editor/react": "^4.7.0",
|
||||
"@tauri-apps/api": "^2.9.1",
|
||||
"ioredis": "^5.8.2",
|
||||
"lucide-react": "^0.294.0",
|
||||
"monaco-editor": "^0.55.1",
|
||||
"sql-formatter": "^15.6.12"
|
||||
"mongodb": "^6.21.0",
|
||||
"mssql": "^11.0.1",
|
||||
"mysql2": "^3.11.0",
|
||||
"pg": "^8.13.0",
|
||||
"sql-formatter": "^15.6.12",
|
||||
"sql.js": "^1.11.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.9.6",
|
||||
"@types/node": "^20.10.0",
|
||||
"@types/react": "^18.2.0",
|
||||
"@types/react-dom": "^18.2.0",
|
||||
"@vitejs/plugin-react": "^4.2.0",
|
||||
"autoprefixer": "^10.4.16",
|
||||
"concurrently": "^9.1.0",
|
||||
"electron": "^33.2.1",
|
||||
"electron-builder": "^25.1.8",
|
||||
"postcss": "^8.4.32",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"tailwindcss": "^3.3.6",
|
||||
"typescript": "^5.3.0",
|
||||
"vite": "^5.0.0"
|
||||
"vite": "^5.0.0",
|
||||
"wait-on": "^8.0.1"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.easysql.app",
|
||||
"productName": "EasySQL",
|
||||
"directories": {
|
||||
"output": "release"
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*",
|
||||
"electron/**/*"
|
||||
],
|
||||
"win": {
|
||||
"target": "nsis",
|
||||
"icon": "public/icon.ico"
|
||||
},
|
||||
"mac": {
|
||||
"target": "dmg",
|
||||
"icon": "public/icon.icns"
|
||||
},
|
||||
"linux": {
|
||||
"target": "AppImage",
|
||||
"icon": "public/icon.png"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
6847
src-tauri/Cargo.lock
generated
6847
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,45 +0,0 @@
|
||||
[package]
|
||||
name = "easysql"
|
||||
version = "1.0.0"
|
||||
description = "A modern database management tool"
|
||||
authors = ["EasySQL"]
|
||||
edition = "2021"
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
tauri = { version = "2", features = ["tray-icon"] }
|
||||
tauri-plugin-shell = "2"
|
||||
tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-single-instance = "2"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
sqlx = { version = "0.8", features = ["runtime-tokio", "mysql", "postgres", "sqlite"] }
|
||||
tiberius = { version = "0.12", default-features = false, features = ["tds73", "rustls", "chrono"] }
|
||||
tokio-util = { version = "0.7", features = ["compat"] }
|
||||
async-std = { version = "1", features = ["attributes"] }
|
||||
ssh2 = "0.9"
|
||||
uuid = { version = "1", features = ["v4"] }
|
||||
dirs = "5"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
thiserror = "1"
|
||||
parking_lot = "0.12"
|
||||
once_cell = "1"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
rfd = "0.14"
|
||||
|
||||
[features]
|
||||
default = ["custom-protocol"]
|
||||
custom-protocol = ["tauri/custom-protocol"]
|
||||
|
||||
[profile.release]
|
||||
panic = "abort"
|
||||
codegen-units = 1
|
||||
lto = true
|
||||
opt-level = "s"
|
||||
strip = true
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
fn main() {
|
||||
tauri_build::build()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -1,31 +0,0 @@
|
||||
use crate::database::ConnectionConfig;
|
||||
use std::fs;
|
||||
use std::path::PathBuf;
|
||||
|
||||
fn get_config_path() -> PathBuf {
|
||||
let config_dir = dirs::config_dir()
|
||||
.unwrap_or_else(|| PathBuf::from("."))
|
||||
.join("easysql");
|
||||
|
||||
fs::create_dir_all(&config_dir).ok();
|
||||
config_dir.join("connections.json")
|
||||
}
|
||||
|
||||
pub fn save_connections(connections: &[ConnectionConfig]) -> Result<(), std::io::Error> {
|
||||
let path = get_config_path();
|
||||
let json = serde_json::to_string_pretty(connections)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))?;
|
||||
fs::write(path, json)
|
||||
}
|
||||
|
||||
pub fn load_connections() -> Result<Vec<ConnectionConfig>, std::io::Error> {
|
||||
let path = get_config_path();
|
||||
if !path.exists() {
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
let content = fs::read_to_string(path)?;
|
||||
serde_json::from_str(&content)
|
||||
.map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e))
|
||||
}
|
||||
|
||||
@ -1,262 +0,0 @@
|
||||
use once_cell::sync::Lazy;
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum DbError {
|
||||
#[error("连接失败: {0}")]
|
||||
ConnectionError(String),
|
||||
#[error("查询失败: {0}")]
|
||||
QueryError(String),
|
||||
#[error("未连接")]
|
||||
NotConnected,
|
||||
#[error("不支持的数据库类型: {0}")]
|
||||
UnsupportedType(String),
|
||||
#[error("SSH 隧道错误: {0}")]
|
||||
SshError(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct ConnectionConfig {
|
||||
pub id: String,
|
||||
#[serde(rename = "type")]
|
||||
pub db_type: String,
|
||||
pub name: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub username: String,
|
||||
pub password: String,
|
||||
pub database: Option<String>,
|
||||
pub ssh_enabled: Option<bool>,
|
||||
pub ssh_host: Option<String>,
|
||||
pub ssh_port: Option<u16>,
|
||||
pub ssh_user: Option<String>,
|
||||
pub ssh_password: Option<String>,
|
||||
pub ssh_key: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TableInfo {
|
||||
pub name: String,
|
||||
pub rows: i64,
|
||||
#[serde(rename = "isView")]
|
||||
pub is_view: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct ColumnInfo {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub data_type: String,
|
||||
pub nullable: bool,
|
||||
pub key: Option<String>,
|
||||
pub comment: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct QueryResult {
|
||||
pub columns: Vec<String>,
|
||||
pub rows: Vec<Vec<serde_json::Value>>,
|
||||
pub error: Option<String>,
|
||||
#[serde(rename = "affectedRows")]
|
||||
pub affected_rows: Option<i64>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct TableDataResult {
|
||||
pub columns: Vec<ColumnInfo>,
|
||||
pub rows: Vec<Vec<serde_json::Value>>,
|
||||
pub total: i64,
|
||||
pub page: i32,
|
||||
#[serde(rename = "pageSize")]
|
||||
pub page_size: i32,
|
||||
}
|
||||
|
||||
// 数据库连接枚举
|
||||
pub enum DbConnection {
|
||||
MySql(sqlx::MySqlPool),
|
||||
Postgres(sqlx::PgPool),
|
||||
Sqlite(sqlx::SqlitePool),
|
||||
SqlServer(SqlServerConnection),
|
||||
}
|
||||
|
||||
pub struct SqlServerConnection {
|
||||
pub config: tiberius::Config,
|
||||
}
|
||||
|
||||
// 连接信息存储
|
||||
pub struct ConnectionInfo {
|
||||
pub connection: DbConnection,
|
||||
pub config: ConnectionConfig,
|
||||
pub ssh_tunnel: Option<crate::ssh::SshTunnel>,
|
||||
}
|
||||
|
||||
// 全局连接管理器
|
||||
pub static CONNECTIONS: Lazy<RwLock<HashMap<String, Arc<ConnectionInfo>>>> =
|
||||
Lazy::new(|| RwLock::new(HashMap::new()));
|
||||
|
||||
pub fn init() {
|
||||
tracing::info!("数据库管理器初始化完成");
|
||||
}
|
||||
|
||||
impl DbConnection {
|
||||
pub async fn test_mysql(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<(), DbError> {
|
||||
let db = database.unwrap_or("mysql");
|
||||
let url = format!("mysql://{}:{}@{}:{}/{}", user, password, host, port, db);
|
||||
|
||||
let pool = sqlx::mysql::MySqlPoolOptions::new()
|
||||
.max_connections(1)
|
||||
.acquire_timeout(std::time::Duration::from_secs(10))
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
sqlx::query("SELECT 1")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| DbError::QueryError(e.to_string()))?;
|
||||
|
||||
pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_postgres(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<(), DbError> {
|
||||
let db = database.unwrap_or("postgres");
|
||||
let url = format!("postgres://{}:{}@{}:{}/{}", user, password, host, port, db);
|
||||
|
||||
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(1)
|
||||
.acquire_timeout(std::time::Duration::from_secs(10))
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
sqlx::query("SELECT 1")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| DbError::QueryError(e.to_string()))?;
|
||||
|
||||
pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_sqlite(path: &str) -> Result<(), DbError> {
|
||||
let url = format!("sqlite:{}?mode=rwc", path);
|
||||
|
||||
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(1)
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
sqlx::query("SELECT 1")
|
||||
.execute(&pool)
|
||||
.await
|
||||
.map_err(|e| DbError::QueryError(e.to_string()))?;
|
||||
|
||||
pool.close().await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn test_sqlserver(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<(), DbError> {
|
||||
use tiberius::{Client, Config, AuthMethod};
|
||||
use tokio::net::TcpStream;
|
||||
use tokio_util::compat::TokioAsyncWriteCompatExt;
|
||||
|
||||
let mut config = Config::new();
|
||||
config.host(host);
|
||||
config.port(port);
|
||||
config.authentication(AuthMethod::sql_server(user, password));
|
||||
if let Some(db) = database {
|
||||
config.database(db);
|
||||
}
|
||||
config.trust_cert();
|
||||
|
||||
let tcp = TcpStream::connect(config.get_addr())
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
tcp.set_nodelay(true).ok();
|
||||
|
||||
let mut client = Client::connect(config, tcp.compat_write())
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
client.simple_query("SELECT 1")
|
||||
.await
|
||||
.map_err(|e| DbError::QueryError(e.to_string()))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn connect_mysql(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<Self, DbError> {
|
||||
let db = database.unwrap_or("mysql");
|
||||
let url = format!("mysql://{}:{}@{}:{}/{}", user, password, host, port, db);
|
||||
|
||||
let pool = sqlx::mysql::MySqlPoolOptions::new()
|
||||
.max_connections(10)
|
||||
.min_connections(1)
|
||||
.acquire_timeout(std::time::Duration::from_secs(30))
|
||||
.idle_timeout(std::time::Duration::from_secs(600))
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
Ok(DbConnection::MySql(pool))
|
||||
}
|
||||
|
||||
pub async fn connect_postgres(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<Self, DbError> {
|
||||
let db = database.unwrap_or("postgres");
|
||||
let url = format!("postgres://{}:{}@{}:{}/{}", user, password, host, port, db);
|
||||
|
||||
let pool = sqlx::postgres::PgPoolOptions::new()
|
||||
.max_connections(10)
|
||||
.min_connections(1)
|
||||
.acquire_timeout(std::time::Duration::from_secs(30))
|
||||
.idle_timeout(std::time::Duration::from_secs(600))
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
Ok(DbConnection::Postgres(pool))
|
||||
}
|
||||
|
||||
pub async fn connect_sqlite(path: &str) -> Result<Self, DbError> {
|
||||
let url = format!("sqlite:{}?mode=rwc", path);
|
||||
|
||||
let pool = sqlx::sqlite::SqlitePoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&url)
|
||||
.await
|
||||
.map_err(|e| DbError::ConnectionError(e.to_string()))?;
|
||||
|
||||
Ok(DbConnection::Sqlite(pool))
|
||||
}
|
||||
|
||||
pub async fn connect_sqlserver(host: &str, port: u16, user: &str, password: &str, database: Option<&str>) -> Result<Self, DbError> {
|
||||
let mut config = tiberius::Config::new();
|
||||
config.host(host);
|
||||
config.port(port);
|
||||
config.authentication(tiberius::AuthMethod::sql_server(user, password));
|
||||
if let Some(db) = database {
|
||||
config.database(db);
|
||||
}
|
||||
config.trust_cert();
|
||||
|
||||
Ok(DbConnection::SqlServer(SqlServerConnection { config }))
|
||||
}
|
||||
}
|
||||
|
||||
// 解析 localhost 为 127.0.0.1
|
||||
pub fn resolve_host(host: &str) -> String {
|
||||
if host == "localhost" {
|
||||
"127.0.0.1".to_string()
|
||||
} else {
|
||||
host.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,74 +0,0 @@
|
||||
// Prevents additional console window on Windows in release
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod database;
|
||||
mod commands;
|
||||
mod config;
|
||||
mod ssh;
|
||||
|
||||
use tauri::Manager;
|
||||
use tracing_subscriber;
|
||||
|
||||
fn main() {
|
||||
// 初始化日志
|
||||
tracing_subscriber::fmt::init();
|
||||
|
||||
tauri::Builder::default()
|
||||
.plugin(tauri_plugin_shell::init())
|
||||
.plugin(tauri_plugin_dialog::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
.plugin(tauri_plugin_single_instance::init(|app, _args, _cwd| {
|
||||
// 当尝试打开第二个实例时,聚焦现有窗口
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
let _ = window.set_focus();
|
||||
let _ = window.unminimize();
|
||||
}
|
||||
}))
|
||||
.setup(|app| {
|
||||
// 初始化数据库连接管理器
|
||||
database::init();
|
||||
|
||||
// 获取主窗口并设置
|
||||
if let Some(window) = app.get_webview_window("main") {
|
||||
// Windows 上启用窗口阴影效果
|
||||
#[cfg(target_os = "windows")]
|
||||
{
|
||||
use tauri::WebviewWindow;
|
||||
let _ = window.set_decorations(false);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
// 窗口控制
|
||||
commands::window_minimize,
|
||||
commands::window_maximize,
|
||||
commands::window_close,
|
||||
// 数据库操作
|
||||
commands::db_test,
|
||||
commands::db_connect,
|
||||
commands::db_disconnect,
|
||||
commands::db_query,
|
||||
commands::db_get_databases,
|
||||
commands::db_get_tables,
|
||||
commands::db_get_columns,
|
||||
commands::db_get_table_data,
|
||||
commands::db_update_row,
|
||||
commands::db_delete_row,
|
||||
commands::db_backup,
|
||||
commands::db_export_table,
|
||||
// 配置操作
|
||||
commands::config_save,
|
||||
commands::config_load,
|
||||
commands::config_export,
|
||||
commands::config_import,
|
||||
// 文件操作
|
||||
commands::file_open,
|
||||
commands::file_save,
|
||||
commands::file_select,
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@ -1,167 +0,0 @@
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::io::{Read, Write};
|
||||
use std::sync::Arc;
|
||||
use std::thread;
|
||||
use ssh2::Session;
|
||||
use thiserror::Error;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum SshError {
|
||||
#[error("连接失败: {0}")]
|
||||
ConnectionError(String),
|
||||
#[error("认证失败: {0}")]
|
||||
AuthError(String),
|
||||
#[error("隧道创建失败: {0}")]
|
||||
TunnelError(String),
|
||||
}
|
||||
|
||||
pub struct SshTunnel {
|
||||
pub local_port: u16,
|
||||
_handle: Option<thread::JoinHandle<()>>,
|
||||
}
|
||||
|
||||
impl SshTunnel {
|
||||
pub async fn create(
|
||||
ssh_host: &str,
|
||||
ssh_port: u16,
|
||||
ssh_user: &str,
|
||||
ssh_password: Option<&str>,
|
||||
ssh_key: Option<&str>,
|
||||
remote_host: &str,
|
||||
remote_port: u16,
|
||||
) -> Result<Self, SshError> {
|
||||
// 找一个可用的本地端口
|
||||
let listener = TcpListener::bind("127.0.0.1:0")
|
||||
.map_err(|e| SshError::TunnelError(e.to_string()))?;
|
||||
let local_port = listener.local_addr()
|
||||
.map_err(|e| SshError::TunnelError(e.to_string()))?
|
||||
.port();
|
||||
|
||||
let ssh_host = ssh_host.to_string();
|
||||
let ssh_user = ssh_user.to_string();
|
||||
let ssh_password = ssh_password.map(|s| s.to_string());
|
||||
let ssh_key = ssh_key.map(|s| s.to_string());
|
||||
let remote_host = remote_host.to_string();
|
||||
|
||||
// 在后台线程中运行隧道
|
||||
let handle = thread::spawn(move || {
|
||||
run_tunnel(
|
||||
listener,
|
||||
&ssh_host,
|
||||
ssh_port,
|
||||
&ssh_user,
|
||||
ssh_password.as_deref(),
|
||||
ssh_key.as_deref(),
|
||||
&remote_host,
|
||||
remote_port,
|
||||
);
|
||||
});
|
||||
|
||||
// 等待一小段时间确保隧道建立
|
||||
tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
|
||||
|
||||
Ok(SshTunnel {
|
||||
local_port,
|
||||
_handle: Some(handle),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn run_tunnel(
|
||||
listener: TcpListener,
|
||||
ssh_host: &str,
|
||||
ssh_port: u16,
|
||||
ssh_user: &str,
|
||||
ssh_password: Option<&str>,
|
||||
ssh_key: Option<&str>,
|
||||
remote_host: &str,
|
||||
remote_port: u16,
|
||||
) {
|
||||
// 连接 SSH 服务器
|
||||
let tcp = match TcpStream::connect(format!("{}:{}", ssh_host, ssh_port)) {
|
||||
Ok(t) => t,
|
||||
Err(e) => {
|
||||
tracing::error!("SSH 连接失败: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut sess = match Session::new() {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
tracing::error!("创建 SSH 会话失败: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
sess.set_tcp_stream(tcp);
|
||||
if let Err(e) = sess.handshake() {
|
||||
tracing::error!("SSH 握手失败: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// 认证
|
||||
let auth_result = if let Some(key_path) = ssh_key {
|
||||
sess.userauth_pubkey_file(ssh_user, None, std::path::Path::new(key_path), None)
|
||||
} else if let Some(password) = ssh_password {
|
||||
sess.userauth_password(ssh_user, password)
|
||||
} else {
|
||||
tracing::error!("SSH 需要密码或密钥");
|
||||
return;
|
||||
};
|
||||
|
||||
if let Err(e) = auth_result {
|
||||
tracing::error!("SSH 认证失败: {}", e);
|
||||
return;
|
||||
}
|
||||
|
||||
let sess = Arc::new(sess);
|
||||
|
||||
// 监听本地连接并转发
|
||||
for stream in listener.incoming() {
|
||||
match stream {
|
||||
Ok(mut local_stream) => {
|
||||
let sess = sess.clone();
|
||||
let remote_host = remote_host.to_string();
|
||||
|
||||
thread::spawn(move || {
|
||||
match sess.channel_direct_tcpip(&remote_host, remote_port, None) {
|
||||
Ok(mut channel) => {
|
||||
let mut buf = [0u8; 8192];
|
||||
loop {
|
||||
// 从本地读取
|
||||
match local_stream.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
if channel.write_all(&buf[..n]).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
|
||||
// 从远程读取
|
||||
match channel.read(&mut buf) {
|
||||
Ok(0) => break,
|
||||
Ok(n) => {
|
||||
if local_stream.write_all(&buf[..n]).is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("创建 SSH 通道失败: {}", e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
tracing::error!("接受本地连接失败: {}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1 +0,0 @@
|
||||
{"rustc_fingerprint":2442455887007604170,"outputs":{"7971740275564407648":{"success":true,"status":"","code":0,"stdout":"___.exe\nlib___.rlib\n___.dll\n___.dll\n___.lib\n___.dll\nC:\\Users\\Ethan\\.rustup\\toolchains\\stable-x86_64-pc-windows-msvc\npacked\n___\ndebug_assertions\npanic=\"unwind\"\nproc_macro\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"msvc\"\ntarget_family=\"windows\"\ntarget_feature=\"cmpxchg16b\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_feature=\"sse3\"\ntarget_has_atomic=\"128\"\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_os=\"windows\"\ntarget_pointer_width=\"64\"\ntarget_vendor=\"pc\"\nwindows\n","stderr":""},"17747080675513052775":{"success":true,"status":"","code":0,"stdout":"rustc 1.92.0 (ded5c06cf 2025-12-08)\nbinary: rustc\ncommit-hash: ded5c06cf21d2b93bffd5d884aa6e96934ee4234\ncommit-date: 2025-12-08\nhost: x86_64-pc-windows-msvc\nrelease: 1.92.0\nLLVM version: 21.1.3\n","stderr":""}},"successes":{}}
|
||||
@ -1,3 +0,0 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
81513b4bc4935786
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"core\", \"default\", \"rustc-dep-of-std\", \"std\"]","target":6569825234462323107,"profile":2225463790103693989,"path":14185826767970760088,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\adler2-963931fa5ded092c\\dep-lib-adler2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
74bf58d300cfc3fe
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"perf-literal\", \"std\"]","declared_features":"[\"default\", \"logging\", \"perf-literal\", \"std\"]","target":7534583537114156500,"profile":15657897354478470176,"path":5842747710153497419,"deps":[[198136567835728122,"memchr",false,14663827455635897954]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\aho-corasick-753d9ff25945fcf1\\dep-lib-aho_corasick","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
df86ee9da5a896b5
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"unsafe\"]","target":1942380541186272485,"profile":15657897354478470176,"path":15208557227947978230,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\alloc-no-stdlib-df988be783143443\\dep-lib-alloc_no_stdlib","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
c83304d10457a342
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"unsafe\"]","target":8756844401079878655,"profile":15657897354478470176,"path":13188036246537209495,"deps":[[9611597350722197978,"alloc_no_stdlib",false,13084831196644280031]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\alloc-stdlib-0b919bce3bf82dab\\dep-lib-alloc_stdlib","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
5a764d98e5da6686
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"alloc\"]","declared_features":"[\"alloc\", \"default\", \"fresh-rust\", \"nightly\", \"serde\", \"std\"]","target":5388200169723499962,"profile":12994027242049262075,"path":9537980989915801285,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\allocator-api2-8865e6a00b4e72ee\\dep-lib-allocator_api2","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
@ -1 +0,0 @@
|
||||
7dea2967765352fa
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[1852463361802237065,"build_script_build",false,11562533667458260168]],"local":[{"RerunIfChanged":{"output":"debug\\build\\anyhow-5f742c575df35aa8\\output","paths":["src/nightly.rs"]}},{"RerunIfEnvChanged":{"var":"RUSTC_BOOTSTRAP","val":null}}],"rustflags":[],"config":0,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
25b95a78b4580001
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":16100955855663461252,"profile":15657897354478470176,"path":4793198230926724775,"deps":[[1852463361802237065,"build_script_build",false,18037571225574304381]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\anyhow-aa601dd51ab6285d\\dep-lib-anyhow","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
@ -1 +0,0 @@
|
||||
c81c58101c5f76a0
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"backtrace\", \"default\", \"std\"]","target":17883862002600103897,"profile":2225463790103693989,"path":16467683319812530953,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\anyhow-d034abc77daf7f05\\dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
4383ca541ec2a2c1
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"portable-atomic\", \"std\"]","target":2348331682808714104,"profile":15657897354478470176,"path":7386643265001597429,"deps":[[1906322745568073236,"pin_project_lite",false,2763931684887686990],[7620660491849607393,"futures_core",false,13482104094475111900],[12100481297174703255,"concurrent_queue",false,9710515496302887928],[17148897597675491682,"event_listener_strategy",false,17468647974699608726]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\async-channel-012b9538802153a7\\dep-lib-async_channel","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
@ -1 +0,0 @@
|
||||
a9b21d6e4790dac2
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"","declared_features":"","target":0,"profile":0,"path":0,"deps":[[15550619062825872913,"build_script_build",false,11785065948707700023]],"local":[{"Precalculated":"2.6.0"}],"rustflags":[],"config":0,"compile_kind":0}
|
||||
@ -1 +0,0 @@
|
||||
373d46570af78ca3
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"tracing\"]","target":5408242616063297496,"profile":4831801323318853768,"path":7761328004364916373,"deps":[[13927012481677012980,"autocfg",false,10807305518230055627]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\async-io-6ca54b3d18dcec1d\\dep-build-script-build-script-build","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
d2c49e4be3350915
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"tracing\"]","target":10084595033463382892,"profile":17582455124764123298,"path":14659531420356540962,"deps":[[5103565458935487,"futures_io",false,952584329958915671],[189982446159473706,"parking",false,15973587823657450042],[6568467691589961976,"windows_sys",false,7246976454518788371],[7667230146095136825,"cfg_if",false,2895588346767177823],[9090520973410485560,"futures_lite",false,2780583266334981273],[12100481297174703255,"concurrent_queue",false,9710515496302887928],[14271827750077741315,"polling",false,3541362140770724921],[14767213526276824509,"slab",false,2368147902018235069],[15550619062825872913,"build_script_build",false,14040693424745460393],[18377328279789821306,"rustix",false,14707779014435926099]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\async-io-ff38d4d37548e5d4\\dep-lib-async_io","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
24de5f045ab4a5e6
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"portable-atomic\", \"std\"]","target":9397226730057430065,"profile":15657897354478470176,"path":10776882996198157094,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\async-task-83aaeca8303bab8f\\dep-lib-async_task","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
91f45a8f710d237a
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":2515742790907851906,"profile":15657897354478470176,"path":14408917710461266522,"deps":[[5157631553186200874,"num_traits",false,2487166575225505192]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\atoi-3b1ff09aa596eaea\\dep-lib-atoi","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
ae00a7479dbd0d3e
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"default\", \"std\"]","declared_features":"[\"default\", \"std\"]","target":2515742790907851906,"profile":15657897354478470176,"path":14408917710461266522,"deps":[[5157631553186200874,"num_traits",false,6589197341473135743]],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\atoi-88fbb6b4610daefc\\dep-lib-atoi","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
599aaabdd810da02
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[\"portable-atomic\"]","target":14411119108718288063,"profile":15657897354478470176,"path":9403445180054510354,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\atomic-waker-0cc2f644b7474aaa\\dep-lib-atomic_waker","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
cbaa38f91b43fb95
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[]","declared_features":"[]","target":6962977057026645649,"profile":2225463790103693989,"path":11879731547235993323,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\autocfg-0837c6aef9c33bfd\\dep-lib-autocfg","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
2f6bf29a2ce2f192
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"alloc\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"std\"]","target":13060062996227388079,"profile":15657897354478470176,"path":13544571482266270501,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\base64-15167b4203d0c3e5\\dep-lib-base64","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
9aeab062cce15bc3
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"std\"]","target":13060062996227388079,"profile":15657897354478470176,"path":17663575163679045343,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\base64-859384f4ec5c3793\\dep-lib-base64","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
1276579c8125b5ad
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"alloc\", \"default\", \"std\"]","declared_features":"[\"alloc\", \"default\", \"std\"]","target":13060062996227388079,"profile":2225463790103693989,"path":13544571482266270501,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\base64-fbf8e75ae7768384\\dep-lib-base64","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
4998c2777483af84
|
||||
@ -1 +0,0 @@
|
||||
{"rustc":7895727629726570510,"features":"[\"alloc\"]","declared_features":"[\"alloc\", \"std\"]","target":15548948006327107948,"profile":15657897354478470176,"path":16698247819254552203,"deps":[],"local":[{"CheckDepInfo":{"dep_info":"debug\\.fingerprint\\base64ct-b7895d159a254796\\dep-lib-base64ct","checksum":false}}],"rustflags":[],"config":2069994364910194474,"compile_kind":0}
|
||||
Binary file not shown.
@ -1 +0,0 @@
|
||||
This file has an mtime of when this was started.
|
||||
@ -1 +0,0 @@
|
||||
65a4f550205e0708
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user