diff --git a/backend/internal/database/migrate.go b/backend/internal/database/migrate.go index 98b8a39..0699e91 100644 --- a/backend/internal/database/migrate.go +++ b/backend/internal/database/migrate.go @@ -9,7 +9,7 @@ import ( "strings" ) -//go:embed migrations/sqlite/0001_initial.sql migrations/sqlite/0002_domain_options.sql migrations/sqlite/0003_deployment.sql migrations/sqlite/0004_task_attempts.sql migrations/sqlite/0005_users.sql migrations/sqlite/0006_server_validation.sql migrations/mysql/0001_initial.sql migrations/mysql/0002_domain_options.sql migrations/mysql/0003_deployment.sql migrations/mysql/0004_task_attempts.sql migrations/mysql/0005_users.sql migrations/mysql/0006_server_validation.sql migrations/postgres/0001_initial.sql migrations/postgres/0002_domain_options.sql migrations/postgres/0003_deployment.sql migrations/postgres/0004_task_attempts.sql migrations/postgres/0005_users.sql migrations/postgres/0006_server_validation.sql +//go:embed migrations/sqlite/0001_initial.sql migrations/sqlite/0002_domain_options.sql migrations/sqlite/0003_deployment.sql migrations/sqlite/0004_task_attempts.sql migrations/sqlite/0005_users.sql migrations/sqlite/0006_server_validation.sql migrations/sqlite/0007_server_password_auth.sql migrations/mysql/0001_initial.sql migrations/mysql/0002_domain_options.sql migrations/mysql/0003_deployment.sql migrations/mysql/0004_task_attempts.sql migrations/mysql/0005_users.sql migrations/mysql/0006_server_validation.sql migrations/mysql/0007_server_password_auth.sql migrations/postgres/0001_initial.sql migrations/postgres/0002_domain_options.sql migrations/postgres/0003_deployment.sql migrations/postgres/0004_task_attempts.sql migrations/postgres/0005_users.sql migrations/postgres/0006_server_validation.sql migrations/postgres/0007_server_password_auth.sql var migrationFiles embed.FS func Migrate(ctx context.Context, db *sql.DB, driver string) error { @@ -38,7 +38,7 @@ func Migrate(ctx context.Context, db *sql.DB, driver string) error { return nil } -const latestMigrationVersion = 6 +const latestMigrationVersion = 7 func applyMigration(ctx context.Context, db *sql.DB, driver string, version int) error { path := fmt.Sprintf("migrations/%s/%04d_%s.sql", driver, version, migrationName(version)) @@ -131,6 +131,8 @@ func migrationName(version int) string { return "users" case 6: return "server_validation" + case 7: + return "server_password_auth" default: return "unknown" } diff --git a/backend/internal/database/migrations/mysql/0007_server_password_auth.sql b/backend/internal/database/migrations/mysql/0007_server_password_auth.sql new file mode 100644 index 0000000..4d88b56 --- /dev/null +++ b/backend/internal/database/migrations/mysql/0007_server_password_auth.sql @@ -0,0 +1,2 @@ +ALTER TABLE servers ADD COLUMN auth_method VARCHAR(32) NOT NULL DEFAULT 'private_key'; +ALTER TABLE servers ADD COLUMN password_ref VARCHAR(255) NOT NULL DEFAULT ''; diff --git a/backend/internal/database/migrations/postgres/0007_server_password_auth.sql b/backend/internal/database/migrations/postgres/0007_server_password_auth.sql new file mode 100644 index 0000000..038abf6 --- /dev/null +++ b/backend/internal/database/migrations/postgres/0007_server_password_auth.sql @@ -0,0 +1,2 @@ +ALTER TABLE servers ADD COLUMN auth_method TEXT NOT NULL DEFAULT 'private_key'; +ALTER TABLE servers ADD COLUMN password_ref TEXT NOT NULL DEFAULT ''; diff --git a/backend/internal/database/migrations/sqlite/0007_server_password_auth.sql b/backend/internal/database/migrations/sqlite/0007_server_password_auth.sql new file mode 100644 index 0000000..038abf6 --- /dev/null +++ b/backend/internal/database/migrations/sqlite/0007_server_password_auth.sql @@ -0,0 +1,2 @@ +ALTER TABLE servers ADD COLUMN auth_method TEXT NOT NULL DEFAULT 'private_key'; +ALTER TABLE servers ADD COLUMN password_ref TEXT NOT NULL DEFAULT ''; diff --git a/backend/internal/deployment/model.go b/backend/internal/deployment/model.go index de57965..5c987d5 100644 --- a/backend/internal/deployment/model.go +++ b/backend/internal/deployment/model.go @@ -12,6 +12,13 @@ import ( type WebServer string +type SSHAuthMethod string + +const ( + SSHAuthPrivateKey SSHAuthMethod = "private_key" + SSHAuthPassword SSHAuthMethod = "password" +) + const ( WebServerNginx WebServer = "nginx" WebServerCaddy WebServer = "caddy" @@ -28,21 +35,24 @@ const ( var serverPathPlaceholderPattern = regexp.MustCompile(`\{[^{}]+\}`) type Server struct { - ID string `json:"id"` - Name string `json:"name"` - Address string `json:"address"` - Port int `json:"port"` - Username string `json:"username"` - PrivateKeyRef string `json:"private_key_ref"` - HostKeyFingerprint string `json:"host_key_fingerprint"` - CertificatePath string `json:"certificate_path"` - PrivateKeyPath string `json:"private_key_path"` - CertificateChainPath string `json:"certificate_chain_path"` - WebServer WebServer `json:"web_server"` - ValidationCommand []string `json:"validation_command,omitempty"` - ReloadCommand []string `json:"reload_command,omitempty"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` + ID string `json:"id"` + Name string `json:"name"` + Address string `json:"address"` + Port int `json:"port"` + Username string `json:"username"` + AuthMethod SSHAuthMethod `json:"auth_method"` + PrivateKeyRef string `json:"private_key_ref"` + PasswordRef string `json:"-"` + PasswordConfigured bool `json:"password_configured"` + HostKeyFingerprint string `json:"host_key_fingerprint"` + CertificatePath string `json:"certificate_path"` + PrivateKeyPath string `json:"private_key_path"` + CertificateChainPath string `json:"certificate_chain_path"` + WebServer WebServer `json:"web_server"` + ValidationCommand []string `json:"validation_command,omitempty"` + ReloadCommand []string `json:"reload_command,omitempty"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` } func (s Server) Normalize() Server { @@ -50,7 +60,17 @@ func (s Server) Normalize() Server { s.Name = strings.TrimSpace(s.Name) s.Address = strings.TrimSpace(s.Address) s.Username = strings.TrimSpace(s.Username) + s.AuthMethod = SSHAuthMethod(strings.ToLower(strings.TrimSpace(string(s.AuthMethod)))) s.PrivateKeyRef = strings.TrimSpace(s.PrivateKeyRef) + s.PasswordRef = strings.TrimSpace(s.PasswordRef) + s.PasswordConfigured = s.PasswordRef != "" + if s.AuthMethod == "" { + if s.PasswordRef != "" { + s.AuthMethod = SSHAuthPassword + } else { + s.AuthMethod = SSHAuthPrivateKey + } + } s.HostKeyFingerprint = strings.TrimSpace(s.HostKeyFingerprint) s.CertificatePath = strings.TrimSpace(s.CertificatePath) s.PrivateKeyPath = strings.TrimSpace(s.PrivateKeyPath) @@ -164,8 +184,17 @@ func (s Server) Validate() error { if s.Username == "" { return errors.New("SSH 用户不能为空") } - if s.PrivateKeyRef == "" { - return errors.New("SSH 私钥引用不能为空") + switch s.AuthMethod { + case SSHAuthPrivateKey: + if s.PrivateKeyRef == "" { + return errors.New("SSH \u79c1\u94a5\u5f15\u7528\u4e0d\u80fd\u4e3a\u7a7a") + } + case SSHAuthPassword: + if s.PasswordRef == "" { + return errors.New("SSH \u5bc6\u7801\u5f15\u7528\u4e0d\u80fd\u4e3a\u7a7a") + } + default: + return fmt.Errorf("SSH \u8ba4\u8bc1\u65b9\u5f0f\u65e0\u6548\uff1a%s", s.AuthMethod) } if s.HostKeyFingerprint == "" { return errors.New("SSH 主机指纹不能为空") diff --git a/backend/internal/deployment/ssh.go b/backend/internal/deployment/ssh.go index 16d7c50..78a2550 100644 --- a/backend/internal/deployment/ssh.go +++ b/backend/internal/deployment/ssh.go @@ -33,23 +33,37 @@ func (f SSHFactory) Connect(ctx context.Context, server Server) (Client, error) if f.Secrets == nil { return nil, errors.New("SSH 敏感信息存储未初始化") } - secrets, err := f.Secrets.Get(ctx, server.PrivateKeyRef) + secretReference := server.PrivateKeyRef + if server.AuthMethod == SSHAuthPassword { + secretReference = server.PasswordRef + } + secrets, err := f.Secrets.Get(ctx, secretReference) if err != nil { - return nil, fmt.Errorf("读取 SSH 私钥失败:%w", err) + return nil, fmt.Errorf("读取 SSH 认证信息失败:%w", err) } - privateKey := strings.TrimSpace(secrets["private_key"]) - if privateKey == "" { - return nil, errors.New("SSH 私钥内容不能为空") - } - var signer ssh.Signer - passphrase := secrets["passphrase"] - if passphrase != "" { - signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) + var authMethods []ssh.AuthMethod + if server.AuthMethod == SSHAuthPassword { + password := secrets["password"] + if password == "" { + return nil, errors.New("SSH 密码不能为空") + } + authMethods = []ssh.AuthMethod{ssh.Password(password)} } else { - signer, err = ssh.ParsePrivateKey([]byte(privateKey)) - } - if err != nil { - return nil, errors.New("解析 SSH 私钥失败:私钥格式或口令无效") + privateKey := strings.TrimSpace(secrets["private_key"]) + if privateKey == "" { + return nil, errors.New("SSH 私钥内容不能为空") + } + var signer ssh.Signer + passphrase := secrets["passphrase"] + if passphrase != "" { + signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(privateKey), []byte(passphrase)) + } else { + signer, err = ssh.ParsePrivateKey([]byte(privateKey)) + } + if err != nil { + return nil, errors.New("解析 SSH 私钥失败:私钥格式或口令无效") + } + authMethods = []ssh.AuthMethod{ssh.PublicKeys(signer)} } timeout := f.Timeout if timeout <= 0 { @@ -61,7 +75,7 @@ func (f SSHFactory) Connect(ctx context.Context, server Server) (Client, error) } timeoutContext, cancel := context.WithTimeout(ctx, timeout) defer cancel() - sshClient, err := connectSSHClient(timeoutContext, server, signer, algorithms) + sshClient, err := connectSSHClient(timeoutContext, server, authMethods, algorithms) if err != nil { return nil, err } @@ -108,7 +122,7 @@ func (f SSHFactory) secureHostKeyAlgorithms() ([]string, error) { func connectSSHClient( ctx context.Context, server Server, - signer ssh.Signer, + authMethods []ssh.AuthMethod, algorithms []string, ) (*ssh.Client, error) { address := net.JoinHostPort(server.Address, fmt.Sprintf("%d", server.Port)) @@ -131,7 +145,7 @@ func connectSSHClient( hostKeyMismatch := "" config := &ssh.ClientConfig{ User: server.Username, - Auth: []ssh.AuthMethod{ssh.PublicKeys(signer)}, + Auth: authMethods, HostKeyAlgorithms: []string{algorithm}, HostKeyCallback: func(_ string, _ net.Addr, key ssh.PublicKey) error { fingerprint := ssh.FingerprintSHA256(key) diff --git a/backend/internal/httpapi/server.go b/backend/internal/httpapi/server.go index ee074a8..041561f 100644 --- a/backend/internal/httpapi/server.go +++ b/backend/internal/httpapi/server.go @@ -377,7 +377,9 @@ type serverRequest struct { Address string `json:"address" binding:"required"` Port int `json:"port"` Username string `json:"username" binding:"required"` - PrivateKeyRef string `json:"private_key_ref" binding:"required"` + AuthMethod string `json:"auth_method"` + PrivateKeyRef string `json:"private_key_ref"` + Password string `json:"password"` HostKeyFingerprint string `json:"host_key_fingerprint" binding:"required"` CertificatePath string `json:"certificate_path"` PrivateKeyPath string `json:"private_key_path"` @@ -700,11 +702,13 @@ func (s *Server) createServer(c *gin.Context) { now := time.Now().UTC() item.CreatedAt = now item.UpdatedAt = now - if err := s.validateSSHKeyReference(c.Request.Context(), item.PrivateKeyRef); err != nil { - s.writeError(c, http.StatusBadRequest, "SSH 私钥引用无效", err) + item, cleanup, err := s.prepareServerCredential(c.Request.Context(), item, request.Password, nil) + if err != nil { + s.writeError(c, http.StatusBadRequest, "SSH 认证信息无效", err) return } if err := s.servers.CreateServer(c.Request.Context(), item); err != nil { + cleanup() s.writeError(c, serverErrorStatus(err), "保存服务器失败", err) return } @@ -735,14 +739,21 @@ func (s *Server) updateServer(c *gin.Context) { } item.CreatedAt = existing.CreatedAt item.UpdatedAt = time.Now().UTC() - if err := s.validateSSHKeyReference(c.Request.Context(), item.PrivateKeyRef); err != nil { - s.writeError(c, http.StatusBadRequest, "SSH 私钥引用无效", err) + item, cleanup, err := s.prepareServerCredential(c.Request.Context(), item, request.Password, &existing) + if err != nil { + s.writeError(c, http.StatusBadRequest, "SSH 认证信息无效", err) return } if err := s.servers.UpdateServer(c.Request.Context(), item); err != nil { + cleanup() s.writeError(c, serverErrorStatus(err), "更新服务器失败", err) return } + if existing.PasswordRef != "" && existing.PasswordRef != item.PasswordRef && s.secrets != nil { + if err := s.secrets.Delete(c.Request.Context(), existing.PasswordRef); err != nil && s.logger != nil { + s.logger.Warn("删除旧 SSH 密码失败", "error", err) + } + } c.JSON(http.StatusOK, item) } @@ -754,11 +765,52 @@ func (s *Server) validateSSHKeyReference(ctx context.Context, reference string) return err } +func (s *Server) prepareServerCredential(ctx context.Context, item deployment.Server, password string, existing *deployment.Server) (deployment.Server, func(), error) { + item = item.Normalize() + cleanup := func() {} + if item.AuthMethod == deployment.SSHAuthPrivateKey { + if err := s.validateSSHKeyReference(ctx, item.PrivateKeyRef); err != nil { + return deployment.Server{}, cleanup, err + } + return item, cleanup, nil + } + if item.AuthMethod != deployment.SSHAuthPassword { + return deployment.Server{}, cleanup, errors.New("SSH \u8ba4\u8bc1\u65b9\u5f0f\u65e0\u6548") + } + if s.secrets == nil { + return deployment.Server{}, cleanup, errors.New("SSH \u5bc6\u7801\u5b58\u50a8\u672a\u521d\u59cb\u5316") + } + if password == "" && existing != nil && existing.PasswordRef != "" { + item.PasswordRef = existing.PasswordRef + return item, cleanup, nil + } + if password == "" { + return deployment.Server{}, cleanup, errors.New("SSH \u5bc6\u7801\u4e0d\u80fd\u4e3a\u7a7a") + } + item.PasswordRef = "secret://ssh/password/" + domain.NewID() + if err := s.secrets.Put(ctx, item.PasswordRef, map[string]string{"kind": "ssh_password", "password": password}); err != nil { + return deployment.Server{}, cleanup, fmt.Errorf("\u4fdd\u5b58 SSH \u5bc6\u7801\u5931\u8d25\uff1a%w", err) + } + cleanup = func() { + _ = s.secrets.Delete(context.Background(), item.PasswordRef) + } + return item, cleanup, nil +} + func (s *Server) deleteServer(c *gin.Context) { if s.servers == nil { s.writeError(c, http.StatusNotImplemented, "服务器管理接口尚未配置", errors.New("服务器仓储未配置")) return } + item, err := s.servers.GetServerByID(c.Request.Context(), c.Param("id")) + if err != nil { + if repository.IsNotFound(err) { + s.writeError(c, http.StatusNotFound, "服务器不存在", err) + return + } + s.writeError(c, http.StatusInternalServerError, "读取服务器失败", err) + return + } if err := s.servers.DeleteServer(c.Request.Context(), c.Param("id")); err != nil { if repository.IsNotFound(err) { s.writeError(c, http.StatusNotFound, "服务器不存在", err) @@ -767,6 +819,11 @@ func (s *Server) deleteServer(c *gin.Context) { s.writeError(c, http.StatusInternalServerError, "删除服务器失败", err) return } + if item.PasswordRef != "" && s.secrets != nil { + if err := s.secrets.Delete(c.Request.Context(), item.PasswordRef); err != nil && s.logger != nil { + s.logger.Warn("删除 SSH 密码失败", "error", err) + } + } c.Status(http.StatusNoContent) } @@ -830,7 +887,7 @@ func (s *Server) replaceDomainServers(c *gin.Context) { func serverFromRequest(request serverRequest) deployment.Server { return deployment.Server{ ID: domain.NewID(), Name: request.Name, Address: request.Address, Port: request.Port, - Username: request.Username, PrivateKeyRef: request.PrivateKeyRef, HostKeyFingerprint: request.HostKeyFingerprint, + Username: request.Username, AuthMethod: deployment.SSHAuthMethod(request.AuthMethod), PrivateKeyRef: request.PrivateKeyRef, HostKeyFingerprint: request.HostKeyFingerprint, CertificatePath: request.CertificatePath, PrivateKeyPath: request.PrivateKeyPath, CertificateChainPath: request.CertificateChainPath, WebServer: deployment.WebServer(request.WebServer), ValidationCommand: request.ValidationCommand, ReloadCommand: request.ReloadCommand, diff --git a/backend/internal/repository/server_repository.go b/backend/internal/repository/server_repository.go index 52dc56d..3fe5b4c 100644 --- a/backend/internal/repository/server_repository.go +++ b/backend/internal/repository/server_repository.go @@ -58,8 +58,8 @@ func (r *SQLServerRepository) CreateServer(ctx context.Context, server deploymen updatedAt = createdAt } _, err = r.db.ExecContext(ctx, r.query(`INSERT INTO servers - (id, name, address, port, username, private_key_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, validation_command_json, reload_command_json, created_at, updated_at) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), server.ID, server.Name, server.Address, server.Port, server.Username, server.PrivateKeyRef, + (id, name, address, port, username, auth_method, private_key_ref, password_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, validation_command_json, reload_command_json, created_at, updated_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`), server.ID, server.Name, server.Address, server.Port, server.Username, server.AuthMethod, server.PrivateKeyRef, server.PasswordRef, server.HostKeyFingerprint, server.CertificatePath, server.PrivateKeyPath, server.CertificateChainPath, server.WebServer, string(validationCommand), string(command), createdAt, updatedAt) if err != nil { return fmt.Errorf("保存服务器失败:%w", err) @@ -84,8 +84,8 @@ func (r *SQLServerRepository) UpdateServer(ctx context.Context, server deploymen if updatedAt.IsZero() { updatedAt = time.Now().UTC() } - result, err := r.db.ExecContext(ctx, r.query(`UPDATE servers SET name = ?, address = ?, port = ?, username = ?, private_key_ref = ?, host_key_fingerprint = ?, certificate_path = ?, private_key_path = ?, certificate_chain_path = ?, web_server = ?, validation_command_json = ?, reload_command_json = ?, updated_at = ? WHERE id = ?`), - server.Name, server.Address, server.Port, server.Username, server.PrivateKeyRef, server.HostKeyFingerprint, server.CertificatePath, server.PrivateKeyPath, server.CertificateChainPath, server.WebServer, string(validationCommand), string(command), updatedAt, server.ID) + result, err := r.db.ExecContext(ctx, r.query(`UPDATE servers SET name = ?, address = ?, port = ?, username = ?, auth_method = ?, private_key_ref = ?, password_ref = ?, host_key_fingerprint = ?, certificate_path = ?, private_key_path = ?, certificate_chain_path = ?, web_server = ?, validation_command_json = ?, reload_command_json = ?, updated_at = ? WHERE id = ?`), + server.Name, server.Address, server.Port, server.Username, server.AuthMethod, server.PrivateKeyRef, server.PasswordRef, server.HostKeyFingerprint, server.CertificatePath, server.PrivateKeyPath, server.CertificateChainPath, server.WebServer, string(validationCommand), string(command), updatedAt, server.ID) if err != nil { return fmt.Errorf("更新服务器失败:%w", err) } @@ -235,8 +235,8 @@ func (r *SQLServerRepository) ReplaceDomainServers(ctx context.Context, domainID return nil } -const serverSelect = `SELECT id, name, address, port, username, private_key_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, validation_command_json, reload_command_json, created_at, updated_at FROM servers` -const serverSelectQualified = `SELECT servers.id, servers.name, servers.address, servers.port, servers.username, servers.private_key_ref, servers.host_key_fingerprint, servers.certificate_path, servers.private_key_path, servers.certificate_chain_path, servers.web_server, servers.validation_command_json, servers.reload_command_json, servers.created_at, servers.updated_at FROM servers` +const serverSelect = `SELECT id, name, address, port, username, auth_method, private_key_ref, password_ref, host_key_fingerprint, certificate_path, private_key_path, certificate_chain_path, web_server, validation_command_json, reload_command_json, created_at, updated_at FROM servers` +const serverSelectQualified = `SELECT servers.id, servers.name, servers.address, servers.port, servers.username, servers.auth_method, servers.private_key_ref, servers.password_ref, servers.host_key_fingerprint, servers.certificate_path, servers.private_key_path, servers.certificate_chain_path, servers.web_server, servers.validation_command_json, servers.reload_command_json, servers.created_at, servers.updated_at FROM servers` type rowScanner interface { Scan(dest ...any) error @@ -245,14 +245,16 @@ type rowScanner interface { func scanServer(row rowScanner) (deployment.Server, error) { var server deployment.Server var webServer string + var authMethod string var validationCommandJSON string var commandJSON string - if err := row.Scan(&server.ID, &server.Name, &server.Address, &server.Port, &server.Username, &server.PrivateKeyRef, &server.HostKeyFingerprint, &server.CertificatePath, &server.PrivateKeyPath, &server.CertificateChainPath, &webServer, &validationCommandJSON, &commandJSON, &server.CreatedAt, &server.UpdatedAt); err != nil { + if err := row.Scan(&server.ID, &server.Name, &server.Address, &server.Port, &server.Username, &authMethod, &server.PrivateKeyRef, &server.PasswordRef, &server.HostKeyFingerprint, &server.CertificatePath, &server.PrivateKeyPath, &server.CertificateChainPath, &webServer, &validationCommandJSON, &commandJSON, &server.CreatedAt, &server.UpdatedAt); err != nil { if errors.Is(err, sql.ErrNoRows) { return deployment.Server{}, sql.ErrNoRows } return deployment.Server{}, fmt.Errorf("读取服务器失败:%w", err) } + server.AuthMethod = deployment.SSHAuthMethod(authMethod) server.WebServer = deployment.WebServer(webServer) if err := json.Unmarshal([]byte(validationCommandJSON), &server.ValidationCommand); err != nil { return deployment.Server{}, fmt.Errorf("解析配置校验命令失败:%w", err) diff --git a/frontend/src/views/Dashboard/Dashboard.js b/frontend/src/views/Dashboard/Dashboard.js index 6862b4c..d6b916c 100644 --- a/frontend/src/views/Dashboard/Dashboard.js +++ b/frontend/src/views/Dashboard/Dashboard.js @@ -336,7 +336,10 @@ export function useDashboard() { address: "", port: 22, username: "", + auth_method: "password", private_key_ref: "", + password: "", + passwordConfigured: false, host_key_fingerprint: "", certificate_path: defaultCertificatePath, private_key_path: defaultPrivateKeyPath, @@ -529,6 +532,7 @@ export function useDashboard() { serverEditingId.value = item.id; serverForm.value = { ...item, + password: "", validation_command: (item.validation_command || []).join("\n"), reload_command: (item.reload_command || []).join("\n"), server_ids: [], @@ -643,6 +647,7 @@ export function useDashboard() { address: serverForm.value.address, port: Number(serverForm.value.port), username: serverForm.value.username, + auth_method: serverForm.value.auth_method, private_key_ref: serverForm.value.private_key_ref, host_key_fingerprint: serverForm.value.host_key_fingerprint, certificate_path: serverForm.value.certificate_path, @@ -652,6 +657,9 @@ export function useDashboard() { validation_command: parseCommand(serverForm.value.validation_command), reload_command: parseCommand(serverForm.value.reload_command), }; + if (serverForm.value.auth_method === "password") { + payload.password = serverForm.value.password; + } const path = serverEditingId.value ? `/api/v1/servers/${serverEditingId.value}` : "/api/v1/servers"; diff --git a/frontend/src/views/Dashboard/ServerModal.vue b/frontend/src/views/Dashboard/ServerModal.vue index d39ce87..6b852be 100644 --- a/frontend/src/views/Dashboard/ServerModal.vue +++ b/frontend/src/views/Dashboard/ServerModal.vue @@ -89,6 +89,25 @@ const { required type="text" /> + + +
+