diff --git a/internal/biz/database.go b/internal/biz/database.go index 86e49a41..5fb7089a 100644 --- a/internal/biz/database.go +++ b/internal/biz/database.go @@ -13,7 +13,7 @@ const ( DatabaseTypePostgresql DatabaseType = "postgresql" DatabaseTypeMongoDB DatabaseType = "mongodb" DatabaseTypeClickHouse DatabaseType = "clickhouse" - DatabaseSQLite DatabaseType = "sqlite" + DatabaseTypeSQLite DatabaseType = "sqlite" DatabaseTypeRedis DatabaseType = "redis" ) diff --git a/internal/data/database.go b/internal/data/database.go index 7d35ce4f..c46053ba 100644 --- a/internal/data/database.go +++ b/internal/data/database.go @@ -45,25 +45,58 @@ func (r *databaseRepo) List(page, limit uint, typ string) ([]*biz.Database, int6 database := make([]*biz.Database, 0) for _, server := range databaseServer { - operator, err := r.getOperator(server) - if err != nil { - continue - } - - if databases, err := operator.Databases(); err == nil { - for item := range slices.Values(databases) { - database = append(database, &biz.Database{ - Type: server.Type, - Name: item.Name, - Server: server.Name, - ServerID: server.ID, - Encoding: item.CharSet, - Comment: item.Comment, - }) + switch server.Type { + case biz.DatabaseTypeMongoDB: + mongo, err := db.NewMongoDB(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if err != nil { + continue } + if databases, err := mongo.Databases(); err == nil { + for item := range slices.Values(databases) { + database = append(database, &biz.Database{ + Type: server.Type, + Name: item.Name, + Server: server.Name, + ServerID: server.ID, + }) + } + } + mongo.Close() + case biz.DatabaseTypeSQLite: + sqlite, err := db.NewSQLite(server.Host) + if err != nil { + continue + } + if tables, err := sqlite.Tables(); err == nil { + for table := range slices.Values(tables) { + database = append(database, &biz.Database{ + Type: server.Type, + Name: table, + Server: server.Name, + ServerID: server.ID, + }) + } + } + sqlite.Close() + default: + operator, err := r.getOperator(server) + if err != nil { + continue + } + if databases, err := operator.Databases(); err == nil { + for item := range slices.Values(databases) { + database = append(database, &biz.Database{ + Type: server.Type, + Name: item.Name, + Server: server.Name, + ServerID: server.ID, + Encoding: item.CharSet, + Comment: item.Comment, + }) + } + } + operator.Close() } - - operator.Close() } if len(database) < int((page-1)*limit) { @@ -127,6 +160,33 @@ func (r *databaseRepo) Create(ctx context.Context, req *request.DatabaseCreate) if err = operator.(*db.Postgres).DatabaseComment(req.Name, req.Comment); err != nil { return err } + case biz.DatabaseTypeClickHouse: + if req.CreateUser { + if err = r.user.Create(ctx, &request.DatabaseUserCreate{ + ServerID: req.ServerID, + Username: req.Username, + Password: req.Password, + }); err != nil { + return err + } + } + if err = operator.DatabaseCreate(req.Name); err != nil { + return err + } + if req.Username != "" { + if err = operator.PrivilegesGrant(req.Username, req.Name); err != nil { + return err + } + } + case biz.DatabaseTypeMongoDB: + mongo, mongoErr := db.NewMongoDB(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if mongoErr != nil { + return mongoErr + } + defer mongo.Close() + if mongoErr = mongo.DatabaseCreate(req.Name); mongoErr != nil { + return mongoErr + } } // 记录日志 @@ -141,14 +201,27 @@ func (r *databaseRepo) Delete(ctx context.Context, serverID uint, name string) e return err } - operator, err := r.getOperator(server) - if err != nil { - return err - } - defer operator.Close() - - if err = operator.DatabaseDrop(name); err != nil { - return err + switch server.Type { + case biz.DatabaseTypeMongoDB: + mongo, mongoErr := db.NewMongoDB(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if mongoErr != nil { + return mongoErr + } + defer mongo.Close() + if mongoErr = mongo.DatabaseDrop(name); mongoErr != nil { + return mongoErr + } + case biz.DatabaseTypeSQLite: + return errors.New(r.t.Get("sqlite does not support dropping tables from here")) + default: + operator, opErr := r.getOperator(server) + if opErr != nil { + return opErr + } + defer operator.Close() + if opErr = operator.DatabaseDrop(name); opErr != nil { + return opErr + } } // 记录日志 @@ -193,6 +266,12 @@ func (r *databaseRepo) getOperator(server *biz.DatabaseServer) (db.Operator, err return nil, err } return postgres, nil + case biz.DatabaseTypeClickHouse: + clickhouse, err := db.NewClickHouse(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if err != nil { + return nil, err + } + return clickhouse, nil default: return nil, fmt.Errorf("unsupported database type: %s", server.Type) } diff --git a/internal/data/database_server.go b/internal/data/database_server.go index d1facce0..d02bca45 100644 --- a/internal/data/database_server.go +++ b/internal/data/database_server.go @@ -191,6 +191,25 @@ func (r *databaseServerRepo) Sync(id uint) error { } } } + case biz.DatabaseTypeClickHouse: + allUsers, err := operator.Users() + if err != nil { + return err + } + for user := range slices.Values(allUsers) { + if !slices.ContainsFunc(users, func(a *biz.DatabaseUser) bool { + return a.Username == user.User + }) && !slices.Contains([]string{"default"}, user.User) { + newUser := &biz.DatabaseUser{ + ServerID: id, + Username: user.User, + Remark: r.t.Get("sync from server %s", server.Name), + } + if err = r.db.Create(newUser).Error; err != nil { + r.log.Warn("sync clickhouse database user failed", slog.String("type", biz.OperationTypeDatabaseServer), slog.Uint64("operator_id", 0), slog.Any("err", err)) + } + } + } } return nil @@ -199,7 +218,7 @@ func (r *databaseServerRepo) Sync(id uint) error { // checkServer 检查服务器连接 func (r *databaseServerRepo) checkServer(server *biz.DatabaseServer) bool { switch server.Type { - case biz.DatabaseTypeMysql, biz.DatabaseTypePostgresql: + case biz.DatabaseTypeMysql, biz.DatabaseTypePostgresql, biz.DatabaseTypeClickHouse: operator, err := r.getOperator(server) if err == nil { operator.Close() @@ -213,6 +232,20 @@ func (r *databaseServerRepo) checkServer(server *biz.DatabaseServer) bool { server.Status = biz.DatabaseServerStatusValid return true } + case biz.DatabaseTypeMongoDB: + mongo, err := db.NewMongoDB(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if err == nil { + mongo.Close() + server.Status = biz.DatabaseServerStatusValid + return true + } + case biz.DatabaseTypeSQLite: + sqlite, err := db.NewSQLite(server.Host) + if err == nil { + sqlite.Close() + server.Status = biz.DatabaseServerStatusValid + return true + } } server.Status = biz.DatabaseServerStatusInvalid @@ -233,6 +266,12 @@ func (r *databaseServerRepo) getOperator(server *biz.DatabaseServer) (db.Operato return nil, err } return postgres, nil + case biz.DatabaseTypeClickHouse: + clickhouse, err := db.NewClickHouse(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if err != nil { + return nil, err + } + return clickhouse, nil default: return nil, fmt.Errorf("unsupported database type: %s", server.Type) } diff --git a/internal/data/database_user.go b/internal/data/database_user.go index 2b7a36e6..cf6e2fcb 100644 --- a/internal/data/database_user.go +++ b/internal/data/database_user.go @@ -223,7 +223,7 @@ func (r *databaseUserRepo) DeleteByNames(serverID uint, names []string) error { } _ = operator.UserDrop(name, host) } - case biz.DatabaseTypePostgresql: + case biz.DatabaseTypePostgresql, biz.DatabaseTypeClickHouse: for name := range slices.Values(names) { _ = operator.UserDrop(name) } @@ -257,6 +257,15 @@ func (r *databaseUserRepo) fillUser(user *biz.DatabaseUser) { } else { user.Status = biz.DatabaseUserStatusInvalid } + case biz.DatabaseTypeClickHouse: + privileges, _ := operator.UserPrivileges(user.Username) + user.Privileges = privileges + if ch2, err := db.NewClickHouse(user.Username, user.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)); err == nil { + ch2.Close() + user.Status = biz.DatabaseUserStatusValid + } else { + user.Status = biz.DatabaseUserStatusInvalid + } } } else { user.Status = biz.DatabaseUserStatusInvalid @@ -282,6 +291,12 @@ func (r *databaseUserRepo) getOperator(server *biz.DatabaseServer) (db.Operator, return nil, err } return postgres, nil + case biz.DatabaseTypeClickHouse: + clickhouse, err := db.NewClickHouse(server.Username, server.Password, fmt.Sprintf("%s:%d", server.Host, server.Port)) + if err != nil { + return nil, err + } + return clickhouse, nil default: return nil, fmt.Errorf("unsupported database type: %s", server.Type) } diff --git a/internal/http/request/database_server.go b/internal/http/request/database_server.go index 0f82033c..a5b8db61 100644 --- a/internal/http/request/database_server.go +++ b/internal/http/request/database_server.go @@ -2,7 +2,7 @@ package request type DatabaseServerCreate struct { Name string `form:"name" json:"name" validate:"required|notExists:database_servers,name|regex:^[a-zA-Z0-9_-]+$"` - Type string `form:"type" json:"type" validate:"required|in:mysql,postgresql,redis"` + Type string `form:"type" json:"type" validate:"required|in:mysql,postgresql,redis,clickhouse,mongodb,sqlite"` Host string `form:"host" json:"host" validate:"required"` Port uint `form:"port" json:"port" validate:"required|min:1|max:65535"` Username string `form:"username" json:"username"` diff --git a/pkg/db/clickhouse.go b/pkg/db/clickhouse.go new file mode 100644 index 00000000..7d224e4e --- /dev/null +++ b/pkg/db/clickhouse.go @@ -0,0 +1,200 @@ +package db + +import ( + "database/sql" + "fmt" + "net/url" + "strings" + + "resty.dev/v3" +) + +// ClickHouse 通过 HTTP API 操作 ClickHouse +type ClickHouse struct { + client *resty.Client + address string + username string + password string +} + +// NewClickHouse 创建 ClickHouse 连接(HTTP API) +func NewClickHouse(username, password, address string) (*ClickHouse, error) { + client := resty.New() + client.SetBaseURL(fmt.Sprintf("http://%s", address)) + client.SetTimeout(10 * 1000 * 1000 * 1000) // 10s + + ch := &ClickHouse{ + client: client, + address: address, + username: username, + password: password, + } + + // 测试连接 + if err := ch.Ping(); err != nil { + _ = client.Close() + return nil, fmt.Errorf("connect to clickhouse failed: %w", err) + } + + return ch, nil +} + +func (r *ClickHouse) Close() { + _ = r.client.Close() +} + +func (r *ClickHouse) Ping() error { + _, err := r.exec("SELECT 1") + return err +} + +func (r *ClickHouse) Query(query string, args ...any) (*sql.Rows, error) { + return nil, fmt.Errorf("clickhouse HTTP API does not support sql.Rows") +} + +func (r *ClickHouse) QueryRow(query string, args ...any) *sql.Row { + return nil +} + +func (r *ClickHouse) Exec(query string, args ...any) (sql.Result, error) { + _, err := r.exec(query) + return nil, err +} + +func (r *ClickHouse) Prepare(query string) (*sql.Stmt, error) { + return nil, fmt.Errorf("clickhouse HTTP API does not support Prepare") +} + +func (r *ClickHouse) DatabaseCreate(name string) error { + _, err := r.exec(fmt.Sprintf("CREATE DATABASE IF NOT EXISTS `%s`", name)) + return err +} + +func (r *ClickHouse) DatabaseDrop(name string) error { + _, err := r.exec(fmt.Sprintf("DROP DATABASE IF EXISTS `%s`", name)) + return err +} + +func (r *ClickHouse) DatabaseExists(name string) (bool, error) { + result, err := r.exec(fmt.Sprintf("SELECT count() FROM system.databases WHERE name = '%s'", name)) + if err != nil { + return false, err + } + return strings.TrimSpace(result) != "0", nil +} + +func (r *ClickHouse) DatabaseSize(name string) (int64, error) { + result, err := r.exec(fmt.Sprintf("SELECT COALESCE(sum(bytes_on_disk), 0) FROM system.parts WHERE database = '%s'", name)) + if err != nil { + return 0, err + } + var size int64 + if _, err = fmt.Sscanf(strings.TrimSpace(result), "%d", &size); err != nil { + return 0, nil + } + return size, nil +} + +func (r *ClickHouse) UserCreate(user, password string, host ...string) error { + _, err := r.exec(fmt.Sprintf("CREATE USER IF NOT EXISTS `%s` IDENTIFIED BY '%s'", user, password)) + return err +} + +func (r *ClickHouse) UserDrop(user string, host ...string) error { + _, err := r.exec(fmt.Sprintf("DROP USER IF EXISTS `%s`", user)) + return err +} + +func (r *ClickHouse) UserPassword(user, password string, host ...string) error { + _, err := r.exec(fmt.Sprintf("ALTER USER `%s` IDENTIFIED BY '%s'", user, password)) + return err +} + +func (r *ClickHouse) UserPrivileges(user string, host ...string) ([]string, error) { + result, err := r.exec(fmt.Sprintf("SHOW GRANTS FOR `%s`", user)) + if err != nil { + return nil, err + } + + var databases []string + for line := range strings.SplitSeq(result, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + // 解析 GRANT ALL ON database.* TO user 格式 + if idx := strings.Index(line, " ON "); idx != -1 { + rest := line[idx+4:] + if dotIdx := strings.Index(rest, "."); dotIdx != -1 { + dbName := strings.Trim(rest[:dotIdx], "`") + if dbName != "" && dbName != "*" { + databases = append(databases, dbName) + } + } + } + } + + return databases, nil +} + +func (r *ClickHouse) PrivilegesGrant(user, database string, host ...string) error { + _, err := r.exec(fmt.Sprintf("GRANT ALL ON `%s`.* TO `%s`", database, user)) + return err +} + +func (r *ClickHouse) PrivilegesRevoke(user, database string, host ...string) error { + _, err := r.exec(fmt.Sprintf("REVOKE ALL ON `%s`.* FROM `%s`", database, user)) + return err +} + +func (r *ClickHouse) Users() ([]User, error) { + result, err := r.exec("SELECT name FROM system.users FORMAT TabSeparated") + if err != nil { + return nil, err + } + + var users []User + for line := range strings.SplitSeq(result, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + users = append(users, User{User: line}) + } + + return users, nil +} + +func (r *ClickHouse) Databases() ([]Database, error) { + result, err := r.exec("SELECT name FROM system.databases WHERE name NOT IN ('system', 'INFORMATION_SCHEMA', 'information_schema') FORMAT TabSeparated") + if err != nil { + return nil, err + } + + var databases []Database + for line := range strings.SplitSeq(result, "\n") { + line = strings.TrimSpace(line) + if line == "" { + continue + } + databases = append(databases, Database{Name: line}) + } + + return databases, nil +} + +// exec 通过 HTTP API 执行 SQL +func (r *ClickHouse) exec(query string) (string, error) { + resp, err := r.client.R(). + SetQueryParam("query", query). + SetQueryParam("user", r.username). + SetQueryParam("password", url.QueryEscape(r.password)). + Get("/") + if err != nil { + return "", fmt.Errorf("clickhouse query failed: %w", err) + } + if resp.StatusCode() != 200 { + return "", fmt.Errorf("clickhouse query error: %s", strings.TrimSpace(resp.String())) + } + return resp.String(), nil +} diff --git a/pkg/db/mongodb.go b/pkg/db/mongodb.go new file mode 100644 index 00000000..2290f8a3 --- /dev/null +++ b/pkg/db/mongodb.go @@ -0,0 +1,160 @@ +package db + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/acepanel/panel/v3/pkg/shell" +) + +// MongoDB 通过 mongosh CLI 操作 MongoDB +type MongoDB struct { + username string + password string + address string // host:port +} + +// NewMongoDB 创建 MongoDB 连接 +func NewMongoDB(username, password, address string) (*MongoDB, error) { + m := &MongoDB{ + username: username, + password: password, + address: address, + } + + if err := m.Ping(); err != nil { + return nil, fmt.Errorf("connect to mongodb failed: %w", err) + } + + return m, nil +} + +func (r *MongoDB) Close() {} + +func (r *MongoDB) Ping() error { + _, err := r.mongosh(`db.runCommand({ping:1})`) + return err +} + +// DatabaseCreate 创建数据库(MongoDB 通过创建集合来显式创建数据库) +func (r *MongoDB) DatabaseCreate(name string) error { + _, err := r.mongosh(fmt.Sprintf(`db.getSiblingDB('%s').createCollection('_init')`, name)) + return err +} + +// DatabaseDrop 删除数据库 +func (r *MongoDB) DatabaseDrop(name string) error { + _, err := r.mongosh(fmt.Sprintf(`db.getSiblingDB('%s').dropDatabase()`, name)) + return err +} + +// Databases 获取数据库列表 +func (r *MongoDB) Databases() ([]MongoDatabase, error) { + raw, err := r.mongosh(`JSON.stringify(db.adminCommand({listDatabases:1,nameOnly:false}))`) + if err != nil { + return nil, err + } + + var result struct { + Databases []struct { + Name string `json:"name"` + SizeOnDisk int64 `json:"sizeOnDisk"` + } `json:"databases"` + } + if err = json.Unmarshal([]byte(raw), &result); err != nil { + return nil, fmt.Errorf("failed to parse databases: %w", err) + } + + var databases []MongoDatabase + for _, db := range result.Databases { + if db.Name == "admin" || db.Name == "config" || db.Name == "local" { + continue + } + databases = append(databases, MongoDatabase{ + Name: db.Name, + SizeOnDisk: db.SizeOnDisk, + }) + } + + return databases, nil +} + +// UserCreate 创建用户 +func (r *MongoDB) UserCreate(user, password, database string) error { + _, err := r.mongosh(fmt.Sprintf(`db.getSiblingDB('%s').createUser({user:'%s',pwd:'%s',roles:[{role:'readWrite',db:'%s'}]})`, database, user, password, database)) + return err +} + +// UserDrop 删除用户 +func (r *MongoDB) UserDrop(user, database string) error { + _, err := r.mongosh(fmt.Sprintf(`db.getSiblingDB('%s').dropUser('%s')`, database, user)) + return err +} + +// UserPassword 修改用户密码 +func (r *MongoDB) UserPassword(user, password string) error { + _, err := r.mongosh(fmt.Sprintf(`db.getSiblingDB('admin').changeUserPassword('%s','%s')`, user, password)) + return err +} + +// Users 获取用户列表 +func (r *MongoDB) Users() ([]MongoUser, error) { + raw, err := r.mongosh(`JSON.stringify(db.getSiblingDB('admin').system.users.find({},{user:1,db:1,roles:1}).toArray())`) + if err != nil { + return nil, err + } + + var result []struct { + User string `json:"user"` + DB string `json:"db"` + Roles []struct { + Role string `json:"role"` + DB string `json:"db"` + } `json:"roles"` + } + if err = json.Unmarshal([]byte(raw), &result); err != nil { + return nil, fmt.Errorf("failed to parse users: %w", err) + } + + var users []MongoUser + for _, u := range result { + var roles []string + for _, role := range u.Roles { + roles = append(roles, fmt.Sprintf("%s@%s", role.Role, role.DB)) + } + users = append(users, MongoUser{ + User: u.User, + DB: u.DB, + Roles: roles, + }) + } + + return users, nil +} + +// mongosh 执行 mongosh 命令 +func (r *MongoDB) mongosh(eval string) (string, error) { + cmd := fmt.Sprintf(`mongosh --quiet --eval "%s" mongodb://%s:%s@%s/admin 2>/dev/null`, + strings.ReplaceAll(eval, `"`, `\"`), + r.username, r.password, r.address, + ) + raw, err := shell.Execf(cmd) + if err != nil { + return "", fmt.Errorf("mongosh error: %w", err) + } + return strings.TrimSpace(raw), nil +} + +// MongoDatabase MongoDB 数据库信息 +type MongoDatabase struct { + Name string `json:"name"` + SizeOnDisk int64 `json:"size_on_disk"` +} + +// MongoUser MongoDB 用户信息 +type MongoUser struct { + User string `json:"user"` + DB string `json:"db"` + Roles []string `json:"roles"` +} diff --git a/pkg/db/sqlite.go b/pkg/db/sqlite.go new file mode 100644 index 00000000..8910469c --- /dev/null +++ b/pkg/db/sqlite.go @@ -0,0 +1,84 @@ +package db + +import ( + "database/sql" + "fmt" + + _ "modernc.org/sqlite" +) + +// SQLite 通过 database/sql 操作 SQLite 文件 +type SQLite struct { + db *sql.DB + path string +} + +// NewSQLite 打开 SQLite 数据库文件 +func NewSQLite(path string) (*SQLite, error) { + db, err := sql.Open("sqlite", path) + if err != nil { + return nil, fmt.Errorf("open sqlite failed: %w", err) + } + if err = db.Ping(); err != nil { + return nil, fmt.Errorf("connect to sqlite failed: %w", err) + } + return &SQLite{db: db, path: path}, nil +} + +func (r *SQLite) Close() { + _ = r.db.Close() +} + +func (r *SQLite) Ping() error { + return r.db.Ping() +} + +// Tables 获取所有表 +func (r *SQLite) Tables() ([]string, error) { + rows, err := r.db.Query("SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name") + if err != nil { + return nil, err + } + defer rows.Close() + + var tables []string + for rows.Next() { + var name string + if err = rows.Scan(&name); err != nil { + continue + } + tables = append(tables, name) + } + return tables, rows.Err() +} + +// SQLiteColumn 表列信息 +type SQLiteColumn struct { + CID int `json:"cid"` + Name string `json:"name"` + Type string `json:"type"` + NotNull bool `json:"not_null"` + Default string `json:"default"` + PrimaryKey bool `json:"primary_key"` +} + +// TableInfo 获取表结构 +func (r *SQLite) TableInfo(name string) ([]SQLiteColumn, error) { + rows, err := r.db.Query(fmt.Sprintf("PRAGMA table_info('%s')", name)) + if err != nil { + return nil, err + } + defer rows.Close() + + var columns []SQLiteColumn + for rows.Next() { + var col SQLiteColumn + var dflt sql.NullString + if err = rows.Scan(&col.CID, &col.Name, &col.Type, &col.NotNull, &dflt, &col.PrimaryKey); err != nil { + continue + } + col.Default = dflt.String + columns = append(columns, col) + } + return columns, rows.Err() +} diff --git a/web/src/views/database/CreateServerModal.vue b/web/src/views/database/CreateServerModal.vue index e9204247..c029d807 100644 --- a/web/src/views/database/CreateServerModal.vue +++ b/web/src/views/database/CreateServerModal.vue @@ -12,6 +12,9 @@ const show = defineModel('show', { type: Boolean, required: true }) const defaultPort = (type: string) => { if (type === 'postgresql') return 5432 if (type === 'redis') return 6379 + if (type === 'clickhouse') return 8123 + if (type === 'mongodb') return 27017 + if (type === 'sqlite') return 0 return 3306 } @@ -28,6 +31,9 @@ const createModel = ref({ const typeOptions = [ { label: 'MySQL', value: 'mysql' }, { label: 'PostgreSQL', value: 'postgresql' }, + { label: 'ClickHouse', value: 'clickhouse' }, + { label: 'MongoDB', value: 'mongodb' }, + { label: 'SQLite', value: 'sqlite' }, { label: 'Redis', value: 'redis' } ] @@ -35,13 +41,7 @@ const typeOptions = [ watch( () => createModel.value.type, (val) => { - if (val === 'postgresql') { - createModel.value.port = 5432 - } else if (val === 'redis') { - createModel.value.port = 6379 - } else { - createModel.value.port = 3306 - } + createModel.value.port = defaultPort(val) } ) @@ -100,46 +100,58 @@ const handleCreate = () => { :placeholder="$gettext('Enter database server name')" /> - - - - - - - - - - - - - - - - - - - + + + + + @@ -35,7 +38,7 @@ const createServerModalShow = ref(false) @@ -54,6 +57,9 @@ const createServerModalShow = ref(false) + + + diff --git a/web/src/views/database/ServerList.vue b/web/src/views/database/ServerList.vue index 85c10a7d..92330f2b 100644 --- a/web/src/views/database/ServerList.vue +++ b/web/src/views/database/ServerList.vue @@ -40,6 +40,15 @@ const openTerminal = (row: any) => { } else { terminalCommand.value = `redis-cli -h '${row.host}' -p '${row.port}'` } + } else if (row.type === 'clickhouse') { + terminalTitle.value = `ClickHouse - ${row.name}` + terminalCommand.value = `clickhouse-client --host '${row.host}' --port 9000 --user '${row.username}' --password '${row.password}'` + } else if (row.type === 'mongodb') { + terminalTitle.value = `MongoDB - ${row.name}` + terminalCommand.value = `mongosh mongodb://${row.username}:${row.password}@${row.host}:${row.port}/admin` + } else if (row.type === 'sqlite') { + terminalTitle.value = `SQLite - ${row.name}` + terminalCommand.value = `sqlite3 '${row.host}'` } else { window.$message.error($gettext('Unsupported database type')) return @@ -161,7 +170,7 @@ const columns: any = [ default: () => $gettext('Terminal') } ), - row.type !== 'redis' + !['redis', 'mongodb', 'sqlite'].includes(row.type) ? h( NPopconfirm, { @@ -214,7 +223,7 @@ const columns: any = [ { onPositiveClick: () => { // 防手贱 - if (['local_mysql', 'local_postgresql', 'local_redis'].includes(row.name)) { + if (['local_mysql', 'local_postgresql', 'local_redis', 'local_clickhouse', 'local_mongodb'].includes(row.name)) { window.$message.error( $gettext( 'Built-in servers cannot be deleted. If you need to delete them, please uninstall the corresponding app' diff --git a/web/src/views/toolbox/ProcessView.vue b/web/src/views/toolbox/ProcessView.vue index 6da5f32a..2409460b 100644 --- a/web/src/views/toolbox/ProcessView.vue +++ b/web/src/views/toolbox/ProcessView.vue @@ -1,6 +1,6 @@