From c7f59f2cbf656ee7d43c540191fbe5b5e6bcd2e2 Mon Sep 17 00:00:00 2001 From: "Logan.Li" Date: Sat, 25 Jul 2026 22:28:16 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8DCSP=E7=99=BD=E5=B1=8F?= =?UTF-8?q?=E5=B9=B6=E4=BC=98=E5=8C=96LDAP=E9=85=8D=E7=BD=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/internal/api/identity.go | 74 ++- backend/internal/ldaputil/address.go | 69 ++ backend/internal/ldaputil/address_test.go | 32 + backend/internal/service/openvpn/openvpn.go | 9 +- frontend/package-lock.json | 659 ++++++++++++++++++++ frontend/package.json | 1 + frontend/src/locales/en-US.json | 30 +- frontend/src/locales/zh-CN.json | 30 +- frontend/src/views/Login.vue | 4 +- frontend/src/views/Plugins.vue | 10 +- frontend/src/views/Users.vue | 71 ++- frontend/vite.config.js | 11 +- 12 files changed, 930 insertions(+), 70 deletions(-) create mode 100644 backend/internal/ldaputil/address.go create mode 100644 backend/internal/ldaputil/address_test.go diff --git a/backend/internal/api/identity.go b/backend/internal/api/identity.go index 20234cb..e4be81b 100644 --- a/backend/internal/api/identity.go +++ b/backend/internal/api/identity.go @@ -6,7 +6,6 @@ import ( "crypto/tls" "encoding/json" "fmt" - "net" "net/http" "strconv" "strings" @@ -19,6 +18,7 @@ import ( "github.com/pquerna/otp/totp" "golang.org/x/crypto/bcrypt" + "vpn-platform/backend/internal/ldaputil" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/service/ikev2" "vpn-platform/backend/internal/service/l2tp" @@ -85,27 +85,20 @@ func ldapSettings() (*model.LDAPConfig, error) { } func ldapConnect(cfg *model.LDAPConfig) (*ldap.Conn, error) { - url := cfg.URL - if !strings.HasPrefix(url, "ldap://") && !strings.HasPrefix(url, "ldaps://") { - // 安全默认:未显式指定协议时按 ldaps,避免口令明文过网 - url = "ldaps://" + url - } - conn, err := ldap.DialURL(url) + dialURL, err := ldaputil.DialURL(cfg.URL) if err != nil { return nil, err } - if cfg.StartTLS && strings.HasPrefix(url, "ldap://") { - host := strings.TrimPrefix(url, "ldap://") - if h, _, err := net.SplitHostPort(host); err == nil { - host = h - } + conn, err := ldap.DialURL(dialURL) + if err != nil { + return nil, err + } + if cfg.StartTLS && strings.HasPrefix(dialURL, "ldap://") { + host := ldaputil.Hostname(dialURL) if err := conn.StartTLS(&tls.Config{ServerName: host}); err != nil { conn.Close() return nil, fmt.Errorf("StartTLS 升级失败: %w", err) } - } else if strings.HasPrefix(url, "ldap://") { - conn.Close() - return nil, fmt.Errorf("明文 ldap:// 存在口令泄露风险,请改用 ldaps:// 或开启 StartTLS") } if cfg.BindDN != "" { if err := conn.Bind(cfg.BindDN, cfg.BindPassword); err != nil { @@ -126,7 +119,12 @@ func (s *Server) authenticateLDAP(username, password string) (*model.User, error return nil, err } defer conn.Close() - filter := fmt.Sprintf("(&%s(%s=%s))", cfg.UserFilter, cfg.UsernameAttribute, ldap.EscapeFilter(username)) + filter := cfg.UserFilter + if strings.Contains(filter, "%s") { + filter = fmt.Sprintf(filter, ldap.EscapeFilter(username)) + } else { + filter = fmt.Sprintf("(&%s(%s=%s))", filter, cfg.UsernameAttribute, ldap.EscapeFilter(username)) + } result, err := conn.Search(ldap.NewSearchRequest(cfg.BaseDN, ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 1, 5, false, filter, []string{"dn", cfg.UsernameAttribute, "cn", "mail"}, nil)) if err != nil || len(result.Entries) != 1 { return nil, fmt.Errorf("LDAP 用户不存在") @@ -361,6 +359,12 @@ func (s *Server) saveLDAPConfig(c *gin.Context) { c.JSON(400, gin.H{"error": "LDAP 地址与 Base DN 必填"}) return } + if req.Enabled { + if _, err := ldaputil.DialURL(req.URL); err != nil { + c.JSON(400, gin.H{"error": err.Error()}) + return + } + } var cfg model.LDAPConfig store.DB.First(&cfg) cfg.URL, cfg.BindDN, cfg.BaseDN, cfg.UserFilter, cfg.UsernameAttribute, cfg.StartTLS, cfg.Enabled = @@ -373,17 +377,35 @@ func (s *Server) saveLDAPConfig(c *gin.Context) { } func (s *Server) testLDAP(c *gin.Context) { - cfg, err := ldapSettings() - if err == nil { - conn, connErr := ldapConnect(cfg) - if connErr == nil { - conn.Close() - c.JSON(200, gin.H{"ok": true}) - return - } - err = connErr + var req struct { + URL string `json:"url"` + BindDN string `json:"bind_dn"` + BindPassword string `json:"bind_password"` + StartTLS bool `json:"starttls"` } - c.JSON(400, gin.H{"error": "LDAP 连接失败: " + err.Error()}) + if c.ShouldBindJSON(&req) != nil || strings.TrimSpace(req.URL) == "" { + c.JSON(400, gin.H{"error": "LDAP 服务器地址必填"}) + return + } + cfg := &model.LDAPConfig{URL: strings.TrimSpace(req.URL), BindDN: strings.TrimSpace(req.BindDN), BindPassword: req.BindPassword, StartTLS: req.StartTLS} + if cfg.BindPassword == "" { + var saved model.LDAPConfig + if store.DB.First(&saved).Error == nil && saved.BindPassword != "" { + plain, err := store.DecryptSecret(saved.BindPassword) + if err != nil { + c.JSON(400, gin.H{"error": "LDAP 配置密钥无效"}) + return + } + cfg.BindPassword = plain + } + } + conn, err := ldapConnect(cfg) + if err != nil { + c.JSON(400, gin.H{"error": "LDAP 连接失败: " + err.Error()}) + return + } + conn.Close() + c.JSON(200, gin.H{"ok": true}) } func (s *Server) listVPNUsers(c *gin.Context) { diff --git a/backend/internal/ldaputil/address.go b/backend/internal/ldaputil/address.go new file mode 100644 index 0000000..0d56d96 --- /dev/null +++ b/backend/internal/ldaputil/address.go @@ -0,0 +1,69 @@ +// Package ldaputil normalizes LDAP server addresses supplied by administrators. +package ldaputil + +import ( + "fmt" + "net" + "net/url" + "strings" +) + +// DialURL accepts an IP address or domain name, with an optional port. Bare +// addresses use LDAPS on port 636. Existing ldap:// and ldaps:// values remain +// supported for backward compatibility. +func DialURL(address string) (string, error) { + address = strings.TrimSpace(address) + if address == "" { + return "", fmt.Errorf("LDAP 服务器地址不能为空") + } + + if strings.Contains(address, "://") { + u, err := url.Parse(address) + if err != nil || (u.Scheme != "ldap" && u.Scheme != "ldaps") || u.Host == "" || u.Path != "" || u.RawQuery != "" || u.Fragment != "" { + return "", fmt.Errorf("LDAP 服务器地址格式无效") + } + port := "389" + if u.Scheme == "ldaps" { + port = "636" + } + host, err := withDefaultPort(u.Host, port) + if err != nil { + return "", err + } + u.Host = host + return u.String(), nil + } + + host, err := withDefaultPort(address, "636") + if err != nil { + return "", err + } + return "ldaps://" + host, nil +} + +func withDefaultPort(address, defaultPort string) (string, error) { + if strings.ContainsAny(address, " \t\r\n/?#@") { + return "", fmt.Errorf("LDAP 服务器地址格式无效") + } + if host, port, err := net.SplitHostPort(address); err == nil { + if host == "" || port == "" { + return "", fmt.Errorf("LDAP 服务器地址格式无效") + } + return address, nil + } + + host := strings.TrimSuffix(strings.TrimPrefix(address, "["), "]") + if host == "" || (strings.Contains(host, ":") && net.ParseIP(host) == nil) { + return "", fmt.Errorf("LDAP 服务器地址格式无效") + } + return net.JoinHostPort(host, defaultPort), nil +} + +// Hostname returns the hostname or IP for TLS ServerName selection. +func Hostname(dialURL string) string { + u, err := url.Parse(dialURL) + if err != nil { + return "" + } + return u.Hostname() +} diff --git a/backend/internal/ldaputil/address_test.go b/backend/internal/ldaputil/address_test.go new file mode 100644 index 0000000..c515c52 --- /dev/null +++ b/backend/internal/ldaputil/address_test.go @@ -0,0 +1,32 @@ +package ldaputil + +import "testing" + +func TestDialURL(t *testing.T) { + tests := []struct { + address string + want string + }{ + {"8.217.205.92", "ldaps://8.217.205.92:636"}, + {"ldap.opsx.vip", "ldaps://ldap.opsx.vip:636"}, + {"ldap.opsx.vip:1636", "ldaps://ldap.opsx.vip:1636"}, + {"2001:db8::1", "ldaps://[2001:db8::1]:636"}, + {"ldap://ldap.opsx.vip", "ldap://ldap.opsx.vip:389"}, + {"ldaps://ldap.opsx.vip", "ldaps://ldap.opsx.vip:636"}, + {" ldaps://8.217.205.92 ", "ldaps://8.217.205.92:636"}, + } + for _, tt := range tests { + got, err := DialURL(tt.address) + if err != nil || got != tt.want { + t.Errorf("DialURL(%q) = %q, %v; want %q", tt.address, got, err, tt.want) + } + } +} + +func TestDialURLRejectsInvalidAddress(t *testing.T) { + for _, address := range []string{"", "ldap.opsx.vip/path", "ldap.opsx.vip:", "https://ldap.opsx.vip"} { + if _, err := DialURL(address); err == nil { + t.Errorf("DialURL(%q) succeeded; want error", address) + } + } +} diff --git a/backend/internal/service/openvpn/openvpn.go b/backend/internal/service/openvpn/openvpn.go index acdd189..3cc8a27 100644 --- a/backend/internal/service/openvpn/openvpn.go +++ b/backend/internal/service/openvpn/openvpn.go @@ -28,6 +28,7 @@ import ( "gorm.io/gorm" "vpn-platform/backend/internal/core" + "vpn-platform/backend/internal/ldaputil" "vpn-platform/backend/internal/model" "vpn-platform/backend/internal/service" "vpn-platform/backend/internal/sessionmgr" @@ -903,11 +904,11 @@ func verifyDialPassword(user *model.VPNUser, password string) error { if err := store.DB.Where("enabled = ?", true).First(&cfg).Error; err != nil { return errors.New("LDAP 未启用") } - url := cfg.URL - if !strings.HasPrefix(url, "ldap://") && !strings.HasPrefix(url, "ldaps://") { - url = "ldap://" + url + dialURL, err := ldaputil.DialURL(cfg.URL) + if err != nil { + return err } - conn, err := ldap.DialURL(url) + conn, err := ldap.DialURL(dialURL) if err != nil { return err } diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 43c2071..7639977 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -18,6 +18,7 @@ "vue-router": "^4.4.5" }, "devDependencies": { + "@intlify/unplugin-vue-i18n": "4.0.0", "@vitejs/plugin-vue": "^5.1.4", "vite": "^5.4.10" } @@ -502,6 +503,35 @@ "integrity": "sha512-HpCo8tmWzLVad5s2d19EhAz5zqrrQ6s69qd6moPMQvkOuSwDT1YgRfWSVuc4ennqrgv3OHppiOGMQ7oC13yIww==", "license": "MIT" }, + "node_modules/@intlify/bundle-utils": { + "version": "8.0.0", + "resolved": "https://registry.npmmirror.com/@intlify/bundle-utils/-/bundle-utils-8.0.0.tgz", + "integrity": "sha512-1B++zykRnMwQ+20SpsZI1JCnV/YJt9Oq7AGlEurzkWJOFtFAVqaGc/oV36PBRYeiKnTbY9VYfjBimr2Vt42wLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@intlify/message-compiler": "^9.4.0", + "@intlify/shared": "^9.4.0", + "acorn": "^8.8.2", + "escodegen": "^2.1.0", + "estree-walker": "^2.0.2", + "jsonc-eslint-parser": "^2.3.0", + "mlly": "^1.2.0", + "source-map-js": "^1.0.1", + "yaml-eslint-parser": "^1.2.2" + }, + "engines": { + "node": ">= 14.16" + }, + "peerDependenciesMeta": { + "petite-vue-i18n": { + "optional": true + }, + "vue-i18n": { + "optional": true + } + } + }, "node_modules/@intlify/core-base": { "version": "9.14.5", "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz", @@ -546,12 +576,90 @@ "url": "https://github.com/sponsors/kazupon" } }, + "node_modules/@intlify/unplugin-vue-i18n": { + "version": "4.0.0", + "resolved": "https://registry.npmmirror.com/@intlify/unplugin-vue-i18n/-/unplugin-vue-i18n-4.0.0.tgz", + "integrity": "sha512-q2Mhqa/mLi0tulfLFO4fMXXvEbkSZpI5yGhNNsLTNJJ41icEGUuyDe+j5zRZIKSkOJRgX6YbCyibTDJdRsukmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@intlify/bundle-utils": "^8.0.0", + "@intlify/shared": "^9.4.0", + "@rollup/pluginutils": "^5.1.0", + "@vue/compiler-sfc": "^3.2.47", + "debug": "^4.3.3", + "fast-glob": "^3.2.12", + "js-yaml": "^4.1.0", + "json5": "^2.2.3", + "pathe": "^1.0.0", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2", + "unplugin": "^1.1.0" + }, + "engines": { + "node": ">= 14.16" + }, + "peerDependencies": { + "petite-vue-i18n": "*", + "vue-i18n": "*", + "vue-i18n-bridge": "*" + }, + "peerDependenciesMeta": { + "petite-vue-i18n": { + "optional": true + }, + "vue-i18n": { + "optional": true + }, + "vue-i18n-bridge": { + "optional": true + } + } + }, "node_modules/@jridgewell/sourcemap-codec": { "version": "1.5.5", "resolved": "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", "license": "MIT" }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmmirror.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/@popperjs/core": { "name": "@sxzz/popperjs-es", "version": "2.11.8", @@ -563,6 +671,29 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@rollup/pluginutils": { + "version": "5.4.0", + "resolved": "https://registry.npmmirror.com/@rollup/pluginutils/-/pluginutils-5.4.0.tgz", + "integrity": "sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, "node_modules/@rollup/rollup-android-arm-eabi": { "version": "4.62.2", "resolved": "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.2.tgz", @@ -1097,6 +1228,29 @@ "vue": "^3.5.0" } }, + "node_modules/acorn": { + "version": "8.17.0", + "resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmmirror.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, "node_modules/agent-base": { "version": "6.0.2", "resolved": "https://registry.npmmirror.com/agent-base/-/agent-base-6.0.2.tgz", @@ -1133,6 +1287,13 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, "node_modules/async-validator": { "version": "4.2.5", "resolved": "https://registry.npmmirror.com/async-validator/-/async-validator-4.2.5.tgz", @@ -1157,6 +1318,19 @@ "proxy-from-env": "^2.1.0" } }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmmirror.com/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmmirror.com/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -1220,6 +1394,13 @@ "node": ">= 0.8" } }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmmirror.com/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/csstype": { "version": "3.2.3", "resolved": "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz", @@ -1425,12 +1606,152 @@ "@esbuild/win32-x64": "0.21.5" } }, + "node_modules/escodegen": { + "version": "2.1.0", + "resolved": "https://registry.npmmirror.com/escodegen/-/escodegen-2.1.0.tgz", + "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esprima": "^4.0.1", + "estraverse": "^5.2.0", + "esutils": "^2.0.2" + }, + "bin": { + "escodegen": "bin/escodegen.js", + "esgenerate": "bin/esgenerate.js" + }, + "engines": { + "node": ">=6.0" + }, + "optionalDependencies": { + "source-map": "~0.6.1" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmmirror.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmmirror.com/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmmirror.com/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "license": "BSD-2-Clause", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmmirror.com/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, "node_modules/estree-walker": { "version": "2.0.2", "resolved": "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", "license": "MIT" }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmmirror.com/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmmirror.com/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.20.1", + "resolved": "https://registry.npmmirror.com/fastq/-/fastq-1.20.1.tgz", + "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "dev": true, + "license": "ISC", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmmirror.com/find-up/-/find-up-4.1.0.tgz", @@ -1614,6 +1935,16 @@ "node": ">= 6" } }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmmirror.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", @@ -1623,6 +1954,84 @@ "node": ">=8" } }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmmirror.com/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/js-yaml": { + "version": "4.3.0", + "resolved": "https://registry.npmmirror.com/js-yaml/-/js-yaml-4.3.0.tgz", + "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmmirror.com/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jsonc-eslint-parser": { + "version": "2.4.2", + "resolved": "https://registry.npmmirror.com/jsonc-eslint-parser/-/jsonc-eslint-parser-2.4.2.tgz", + "integrity": "sha512-1e4qoRgnn448pRuMvKGsFFymUCquZV0mpGgOyIKNgD3JVDTsVJyRBGH/Fm0tBb8WsWGgmB1mDe6/yJMQM37DUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.5.0", + "eslint-visitor-keys": "^3.0.0", + "espree": "^9.0.0", + "semver": "^7.3.5" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/locate-path": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/locate-path/-/locate-path-5.0.0.tgz", @@ -1682,6 +2091,43 @@ "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmmirror.com/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.8", + "resolved": "https://registry.npmmirror.com/micromatch/-/micromatch-4.0.8.tgz", + "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", + "dev": true, + "license": "MIT", + "dependencies": { + "braces": "^3.0.3", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/micromatch/node_modules/picomatch": { + "version": "2.3.2", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmmirror.com/mime-db/-/mime-db-1.52.0.tgz", @@ -1703,6 +2149,26 @@ "node": ">= 0.6" } }, + "node_modules/mlly": { + "version": "1.8.2", + "resolved": "https://registry.npmmirror.com/mlly/-/mlly-1.8.2.tgz", + "integrity": "sha512-d+ObxMQFmbt10sretNDytwt85VrbkhhUA/JBGm1MPaWJ65Cl4wOgLaB1NYvJSZ0Ef03MMEU/0xpPMXUIQ29UfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.16.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.3" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmmirror.com/ms/-/ms-2.1.3.tgz", @@ -1778,12 +2244,51 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", "license": "ISC" }, + "node_modules/picomatch": { + "version": "4.0.5", + "resolved": "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmmirror.com/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmmirror.com/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/pngjs": { "version": "5.0.0", "resolved": "https://registry.npmmirror.com/pngjs/-/pngjs-5.0.0.tgz", @@ -1847,6 +2352,27 @@ "node": ">=10.13.0" } }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmmirror.com/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmmirror.com/require-directory/-/require-directory-2.1.1.tgz", @@ -1862,6 +2388,17 @@ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", "license": "ISC" }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmmirror.com/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "dev": true, + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, "node_modules/rollup": { "version": "4.62.2", "resolved": "https://registry.npmmirror.com/rollup/-/rollup-4.62.2.tgz", @@ -1907,12 +2444,60 @@ "fsevents": "~2.3.2" } }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmmirror.com/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmmirror.com/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmmirror.com/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", "license": "ISC" }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmmirror.com/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "license": "BSD-3-Clause", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz", @@ -1948,12 +2533,46 @@ "node": ">=8" } }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, "node_modules/tslib": { "version": "2.3.0", "resolved": "https://registry.npmmirror.com/tslib/-/tslib-2.3.0.tgz", "integrity": "sha512-N82ooyxVNm6h1riLCoyS9e3fuJ3AMG2zIZs2Gd1ATcSFjSA23Q0fzjjZeh0jbJvWVDZ0cJT8yaNNaaXHzueNjg==", "license": "0BSD" }, + "node_modules/ufo": { + "version": "1.6.4", + "resolved": "https://registry.npmmirror.com/ufo/-/ufo-1.6.4.tgz", + "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", + "dev": true, + "license": "MIT" + }, + "node_modules/unplugin": { + "version": "1.16.1", + "resolved": "https://registry.npmmirror.com/unplugin/-/unplugin-1.16.1.tgz", + "integrity": "sha512-4/u/j4FrCKdi17jaxuJA0jClGxB1AvU2hw/IuayPc4ay1XGaJs/rbb4v5WKwAjNifjmXK9PIFyuPiaK8azyR9w==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.14.0", + "webpack-virtual-modules": "^0.6.2" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz", @@ -2077,6 +2696,13 @@ "vue": "^3.5.0" } }, + "node_modules/webpack-virtual-modules": { + "version": "0.6.2", + "resolved": "https://registry.npmmirror.com/webpack-virtual-modules/-/webpack-virtual-modules-0.6.2.tgz", + "integrity": "sha512-66/V2i5hQanC51vBQKPH4aI8NMAcBW59FVBs+rC7eGHupMyfn34q7rZIE+ETlJ+XTevqfUhVVBgSUNSW2flEUQ==", + "dev": true, + "license": "MIT" + }, "node_modules/which-module": { "version": "2.0.1", "resolved": "https://registry.npmmirror.com/which-module/-/which-module-2.0.1.tgz", @@ -2103,6 +2729,39 @@ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", "license": "ISC" }, + "node_modules/yaml": { + "version": "2.9.0", + "resolved": "https://registry.npmmirror.com/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yaml-eslint-parser": { + "version": "1.3.2", + "resolved": "https://registry.npmmirror.com/yaml-eslint-parser/-/yaml-eslint-parser-1.3.2.tgz", + "integrity": "sha512-odxVsHAkZYYglR30aPYRY4nUGJnoJ2y1ww2HDvZALo0BDETv9kWbi16J52eHs+PWRNmF4ub6nZqfVOeesOvntg==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.0.0", + "yaml": "^2.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ota-meshi" + } + }, "node_modules/yargs": { "version": "15.4.1", "resolved": "https://registry.npmmirror.com/yargs/-/yargs-15.4.1.tgz", diff --git a/frontend/package.json b/frontend/package.json index d9e80b3..a1bee67 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -18,6 +18,7 @@ "vue-router": "^4.4.5" }, "devDependencies": { + "@intlify/unplugin-vue-i18n": "4.0.0", "@vitejs/plugin-vue": "^5.1.4", "vite": "^5.4.10" } diff --git a/frontend/src/locales/en-US.json b/frontend/src/locales/en-US.json index 4301cc7..c7b1e59 100644 --- a/frontend/src/locales/en-US.json +++ b/frontend/src/locales/en-US.json @@ -73,7 +73,7 @@ "passwordLabel": "Password", "passwordPlaceholder": "Enter password", "loginButton": "Sign In", - "defaultAccount": "Default account: admin / admin123 — please change password after first login", + "defaultAccount": "Default account: {account} — please change password after first login", "errorEmpty": "Please enter username and password", "loginSuccess": "Login successful", "changePasswordFirst": "Please change your password first", @@ -279,14 +279,22 @@ "roleOperator": "Operator", "roleViewer": "Viewer", "noUsers": "No users yet", - "ldapTitle": "LDAP Identity Source", + "ldapTitle": "LDAP / Active Directory", "ldapHint": "LDAP users can be auto-synced on first login, or pre-imported using the \"LDAP Import\" on the left.", - "ldapUrl": "LDAP URL", - "bindDn": "Bind DN", - "bindPassword": "Bind Password", + "ldapConfigHint": "Configure an LDAP server for centralized identity authentication", + "ldapEnabled": "Enable LDAP", + "ldapServerAddress": "LDAP Server Address", + "ldapServerAddressPlaceholder": "e.g. 8.217.205.92 or ldap.example.com", + "ldapPort": "Port", + "ldapProtocol": "Protocol", + "ldapProtocolLDAPS": "LDAPS (Recommended)", + "ldapProtocolLDAP": "LDAP", + "bindDn": "Administrator Bind DN", + "bindPassword": "Administrator Password", "keepOriginal": "Leave empty to keep original", - "baseDn": "Base DN", - "userFilter": "User Filter", + "baseDn": "Base DN (User Search Base)", + "userFilter": "Search Filter (optional; %s is the username)", + "enableLdaps": "Enable LDAPS (SSL encryption)", "usernameAttr": "Username Attribute", "testConnection": "Test Connection", "editUser": "Edit User", @@ -320,6 +328,7 @@ "ldapLoadFailed": "LDAP load failed", "ldapConfigSaved": "LDAP configuration saved", "ldapConnected": "LDAP connection successful", + "ldapTestRequired": "Test the LDAP connection successfully before saving", "ldapConnectFailed": "Connection failed", "emptyUsername": "(empty)", "errorDetails": "Error details:" @@ -502,8 +511,9 @@ "plugins": { "title": "Custom Protocol Plugins", "registerPlugin": "Register Plugin", - "pluginHint": "Plugins implement the unified protocol contract via command hooks: the platform pipes tunnel config JSON to hook commands via stdin.", - "pluginContract": "Contract: Create / Start / Stop / Status / Config; Status hook outputs running or stopped on stdout; Config hook outputs client config text.", + "commandHooks": "command hooks", + "pluginHint": "Plugins implement the unified protocol contract via {hooks}: the platform pipes tunnel config JSON to hook commands via stdin.", + "pluginContract": "Contract: {contract}; Status hook outputs {running} or {stopped} on stdout; Config hook outputs client config text.", "protocolId": "Protocol ID", "enabled": "Enabled", "disabled": "Disabled", @@ -1024,4 +1034,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/src/locales/zh-CN.json b/frontend/src/locales/zh-CN.json index eb9a87f..53283c6 100644 --- a/frontend/src/locales/zh-CN.json +++ b/frontend/src/locales/zh-CN.json @@ -73,7 +73,7 @@ "passwordLabel": "密码", "passwordPlaceholder": "请输入密码", "loginButton": "登 录", - "defaultAccount": "默认账号 admin / admin123,登录后请立即修改", + "defaultAccount": "默认账号 {account},登录后请立即修改", "errorEmpty": "请输入用户名和密码", "loginSuccess": "登录成功", "changePasswordFirst": "首次登录请先修改密码", @@ -279,14 +279,22 @@ "roleOperator": "运维", "roleViewer": "只读", "noUsers": "还没有用户", - "ldapTitle": "LDAP 身份源", + "ldapTitle": "LDAP / Active Directory", "ldapHint": "LDAP 用户可首次登录后自动同步,也可使用左侧「LDAP 导入」预先添加。", - "ldapUrl": "LDAP URL", - "bindDn": "Bind DN", - "bindPassword": "Bind 密码", + "ldapConfigHint": "配置 LDAP 服务器,实现统一身份认证", + "ldapEnabled": "启用 LDAP", + "ldapServerAddress": "LDAP 服务器地址", + "ldapServerAddressPlaceholder": "例如 8.217.205.92 或 ldap.example.com", + "ldapPort": "端口", + "ldapProtocol": "连接协议", + "ldapProtocolLDAPS": "LDAPS(推荐)", + "ldapProtocolLDAP": "LDAP", + "bindDn": "管理员 Bind DN", + "bindPassword": "管理员密码", "keepOriginal": "留空保持原值", - "baseDn": "Base DN", - "userFilter": "用户过滤", + "baseDn": "Base DN(用户搜索基础)", + "userFilter": "搜索过滤器(可选,%s 表示用户名)", + "enableLdaps": "启用 LDAPS(SSL 加密)", "usernameAttr": "用户名属性", "testConnection": "测试连接", "editUser": "编辑用户", @@ -320,6 +328,7 @@ "ldapLoadFailed": "LDAP 加载失败", "ldapConfigSaved": "LDAP 配置已保存", "ldapConnected": "LDAP 连接成功", + "ldapTestRequired": "请先测试 LDAP 连接,通过后才能保存", "ldapConnectFailed": "连接失败", "emptyUsername": "(空)", "errorDetails": "失败详情:" @@ -502,8 +511,9 @@ "plugins": { "title": "自定义协议插件", "registerPlugin": "注册插件", - "pluginHint": "插件通过命令钩子实现统一协议契约:平台将专线配置 JSON 通过 stdin 传给钩子命令。", - "pluginContract": "契约:Create / Start / Stop / Status / Config;Status 钩子 stdout 输出 running 或 stopped;Config 钩子 stdout 输出客户端配置文本。", + "commandHooks": "命令钩子", + "pluginHint": "插件通过 {hooks} 实现统一协议契约:平台将专线配置 JSON 通过 stdin 传给钩子命令。", + "pluginContract": "契约:{contract};Status 钩子 stdout 输出 {running} 或 {stopped};Config 钩子 stdout 输出客户端配置文本。", "protocolId": "协议标识", "enabled": "启用", "disabled": "停用", @@ -1024,4 +1034,4 @@ } } } -} \ No newline at end of file +} diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 53dfca5..48c56af 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -72,7 +72,9 @@ diff --git a/frontend/src/views/Plugins.vue b/frontend/src/views/Plugins.vue index 03c29df..1081dbd 100644 --- a/frontend/src/views/Plugins.vue +++ b/frontend/src/views/Plugins.vue @@ -8,8 +8,14 @@ -

-

+ + + + + + + +
diff --git a/frontend/src/views/Users.vue b/frontend/src/views/Users.vue index 7495646..2045758 100644 --- a/frontend/src/views/Users.vue +++ b/frontend/src/views/Users.vue @@ -46,20 +46,28 @@ - - - - - - + + + + + + + + + + + - - - + +
- {{ $t('users.testConnection') }} - {{ $t('common.save') }} + {{ $t('users.testConnection') }} + {{ $t('common.save') }}
@@ -147,7 +155,7 @@